I am writing an object that must always have certain values. Most notably, it must always have a value for Name
property.
public class User
{
public string Name { get; set; }
public User(string name)
{
Name = name;
}
}
Now, there are a couple of business rules that I need to implement in this class. One of which is that the Name
property must be a unique name. So, I would think that the initializer for this object would look something this:
public User(string name, IQueryable<User> allUsers)
{
var matches = allUsers.Where(q => q.Name == name).ToList();
if(matches.Any())
{
// abort object initialization
}
Name = name;
}
But I'm not sure how I would abort the object initialization. In fact, is that even possible?
Is there a way to abort an object initialization (ie: set object to null) or is there a better way of accomplishing this?
Well, you'd just throw an exception. But I don't like this way of handling this problem at all. Rather, you should be creating the user through the service, and have the service check if the name is valid or not.
I suppose you could check and throw an exception in the object's constructor or Name setter, but eeeehhhh that can come with a host of issues and mixed concerns. I say create the object through a factory that does this check and returns null (or a nicely named exception). Or create the POCO object and do the validation via a separate class/method.
Aborting initialization of an object is done by throwing an exception in the constructor, and is recommended to reject invalid input.
public class User
{
public User(String name) {
if (String.IsNullOrWhiteSpace(name)) {
if (name == null) {
throw new System.ArgumentNullException("Cannot be null.", "name");
}
else {
throw new System.ArgumentException("Cannot be empty.", "name");
}
}
}
}
The business logic you wish to define in the constructor doesn't fit there. Constructors should be lightweight, and instantiation only. Querying some data source is too expensive for a constructor. Because of this, you should use the factory pattern instead. With the factory pattern, a caller might expect there to be some heavy lifting involved with object creation.
public class User
{
private User(String name) {
if (String.IsNullOrWhiteSpace(name)) {
if (name == null) {
throw new System.ArgumentNullException("Cannot be null.", "name");
}
else {
throw new System.ArgumentException("Cannot be empty.", "name");
}
}
}
public static User CreateUser(String name) {
User user = new User(name); // Lightweight instantiation, basic validation
var matches = allUsers.Where(q => q.Name == name).ToList();
if(matches.Any())
{
throw new System.ArgumentException("User with the specified name already exists.", "name");
}
Name = name;
}
public String Name {
get;
private set; // Optionally public if needed
}
}
You can see that the factory pattern fits better, and because it's a method a caller might expect there to be some work going on by invoking it. Whereas with a constructor one would expect it to be lightweight.
If you wanted to go the constructor route, then you would want to try some other method of enforcing your business rules such as when the actual insert into the data source is attempted.
public class User
{
public User(String name) {
if (String.IsNullOrWhiteSpace(name)) {
if (name == null) {
throw new System.ArgumentNullException("Cannot be null.", "name");
}
else {
throw new System.ArgumentException("Cannot be empty.", "name");
}
}
}
}
public class SomeDataSource {
public void AddUser(User user) {
// Do your business validation here, and either throw or possibly return a value
// If business rules pass, then add the user
Users.Add(user);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With