In the previous post, we talked about the different ways of creating an object of a class. In this post, we will weight them on several parameters.
Line of Code: When you use a constructor, you don’t need to add any extra code just a constructor.
public class Customer
{
private string _id;
private string _firstName;
private string _lastName;
private string _email;
public Customer(string id,
string firstName, string lastName, string email)
{
_id = id;
_firstName = firstName;
_lastName = lastName;
_email = email;
}
}
When you use a mapper, you have to add a profile for the mapping or need to expose additional setters to set the data.
When you use the factory method, you have to add an extra method, to begin with. Depending upon the language you are using, you might need to add a similar constructor as well.
You also might end up with more than one factory method.
public class Customer
{
private string _id;
private string _firstName;
private string _lastName;
private string _email;
private Customer()
{
}
public static Customer SignUp(string firstName,
string lastName, string email)
{
return new Customer
{
_firstName = firstName,
_lastName = lastName,
_email = email
};
}
public static Customer LoadFromPersistence(string id,
string firstName, string lastName, string email)
{
return new Customer
{
_id = id,
_firstName = firstName,
_lastName = lastName,
_email = email
};
}
}
Its pity evident from the code snippets that we can work things around with one constructor but we have to add two factory methods.
If your concern is line of code (LOC), constructor is a hands down winner.
Readability: Let’s use the code of previous post once again.
public Customer SignUp(SignUpRequest signUpRequest)
{
return new Customer(null, signUpRequest.FirstName,
signUpRequest.LastName,
signUpRequest.Email);
}
When we are using a constructor, the intent is not visible at all. We cannot differentiate between a new customer registration and customer load from persistence.
Using language syntax is like magic and like the real world, magic is never easy to understand.
public Customer SignUp(SignUpRequest signUpRequest)
{
return Customer.SignUp(signUpRequest.FirstName,
signUpRequest.LastName,
signUpRequest.Email);
}
When we are using a factory method, behavior/ intent is pity evident and hence better readable.
In terms of readability, Factory Method is a clear winner.
Maintainability: Once a great man said, “Walking on water and building software for requirements are easy if both are frozen.” Changing requirements are the reality of the industry. Let’s say, now we have the requirements of customers to sign up with mobile or email.
We have to make the following changes in our constructor approach.
public class Customer
{
private string _id;
private string _firstName;
private string _lastName;
private string _email;
private string _mobileNumber;
public Customer(string id,
string firstName, string lastName, string email,
string mobileNumber)
{
if (!string.IsNullOrEmpty(email) &&
!string.IsNullOrEmpty(mobileNumber))
{
throw new InvalidCustomerRequestException(
"Customer can sign up using email or mobile not
both");
}
_id = id;
_firstName = firstName;
_lastName = lastName;
_email = email;
_mobileNumber = mobileNumber;
}
}
We have added check in constructor to cater new requirement. We cannot add another constructor as its same number of parameters, hence constructor overloading will not work.
We shall face similar problem in mapper. We have to add a validation somewhere to adjust according to requirements.
We have to make following change in factory method approach.
public class Customer
{
private string _id;
private string _firstName;
private string _lastName;
private string _email;
private string _mobileNumber;
private Customer()
{
}
public static Customer SignUpWithEmail(string firstName,
string lastName, string email)
{
return new Customer
{
_firstName = firstName,
_lastName = lastName,
_email = email
};
}
public static Customer SignUpWithMobile(string firstName,
string lastName, string mobileNumber)
{
return new Customer
{
_firstName = firstName,
_lastName = lastName,
_mobileNumber = mobileNumber
};
}
}
We can just add a new method and rename old method to cater new requirements.
Even here, factory method is a clear winner.
After assessing them on these three parameters, we can conclude following.
- Wherever we need readability and maintainability, we should go with factory methods. For example Domain Model.
- Whenever we are eyeing for less code due to lack of complexity, we should go with constructors. For example value objects with no or little behaviour.
- Whenever we don’t care about object creation, we should go for syntax sugar.For example DTO & DAO.