How many constructor arguments is too many? [closed]
Let's say you have a class called Customer, which contains the following fields:
- UserName
- First Name
- Last Name
Let's also say that according to your business logic, all Customer objects must have these four properties defined.
Now, we can do this pretty easily by forcing the constructor to specify each of these properties. But it's pretty easy to see how this can spiral out of control when you are forced to add more required fields to the Customer object.
I've seen classes that take in 20+ arguments into their constructor and it's just a pain to use them. But, alternatively, if you don't require these fields you run into the risk of having undefined information, or worse, object referencing errors if you rely on the calling code to specify these properties.
Are there any alternatives to this or do you you just have to decide whether X amount of constructor arguments is too many for you to live with?
Two design approaches to consider
The essence pattern
The fluent interface pattern
These are both similar in intent, in that we slowly build up an intermediate object, and then create our target object in a single step.
An example of the fluent interface in action would be:
public class CustomerBuilder {
String surname;
String firstName;
String ssn;
public static CustomerBuilder customer() {
return new CustomerBuilder();
}
public CustomerBuilder withSurname(String surname) {
this.surname = surname;
return this;
}
public CustomerBuilder withFirstName(String firstName) {
this.firstName = firstName;
return this;
}
public CustomerBuilder withSsn(String ssn) {
this.ssn = ssn;
return this;
}
// client doesn't get to instantiate Customer directly
public Customer build() {
return new Customer(this);
}
}
public class Customer {
private final String firstName;
private final String surname;
private final String ssn;
Customer(CustomerBuilder builder) {
if (builder.firstName == null) throw new NullPointerException("firstName");
if (builder.surname == null) throw new NullPointerException("surname");
if (builder.ssn == null) throw new NullPointerException("ssn");
this.firstName = builder.firstName;
this.surname = builder.surname;
this.ssn = builder.ssn;
}
public String getFirstName() { return firstName; }
public String getSurname() { return surname; }
public String getSsn() { return ssn; }
}
import static com.acme.CustomerBuilder.customer;
public class Client {
public void doSomething() {
Customer customer = customer()
.withSurname("Smith")
.withFirstName("Fred")
.withSsn("123XS1")
.build();
}
}
I see that some people are recommending seven as an upper limit. Apparently it is not true that people can hold seven things in their head at once; they can only remember four (Susan Weinschenk, 100 Things Every Designer Needs to Know about People, 48). Even so, I consider four to be something of a high earth orbit. But that's because my thinking has been altered by Bob Martin.
In Clean Code, Uncle Bob argues for three as an general upper limit for number of parameters. He makes the radical claim (40):
The ideal number of arguments for a function is zero (niladic). Next comes one (monadic) followed closely by two (dyadic). Three arguments (triadic) should be avoided where possible. More than three (polyadic) requires very special justification—and then shouldn't be used anyway.
He says this because of readability; but also because of testability:
Imagine the difficulty of writing all the test cases to ensure that all various combinations of arguments work properly.
I encourage you to find a copy of his book and read his full discussion of function arguments (40-43).
I agree with those who have mentioned the Single Responsibility Principle. It is hard for me to believe that a class that needs more than two or three values/objects without reasonable defaults really has only one responsibility, and would not be better off with another class extracted.
Now, if you are injecting your dependencies through the constructor, Bob Martin's arguments about how easy it is to invoke the constructor do not so much apply (because usually then there is only one point in your application where you wire that up, or you even have a framework that does it for you). However, the Single Responsibility Principle is still relevant: once a class has four dependencies, I consider that a smell that it is doing a large amount of work.
However, as with all things in computer science, there are doubtless valid cases for having a large number of constructor parameters. Don't contort your code to avoid using a large number of parameters; but if you do use a large number of parameters, stop and give it some thought, because it may mean your code is already contorted.
In your case, stick with the constructor. The information belongs in Customer and 4 fields are fine.
In the case you have many required and optional fields the constructor is not the best solution. As @boojiboy said, it's hard to read and it's also hard to write client code.
@contagious suggested using the default pattern and setters for optional attributs. That mandates that the fields are mutable, but that's a minor problem.
Joshua Block on Effective Java 2 say that in this case you should consider a builder. An example taken from the book:
public class NutritionFacts {
private final int servingSize;
private final int servings;
private final int calories;
private final int fat;
private final int sodium;
private final int carbohydrate;
public static class Builder {
// required parameters
private final int servingSize;
private final int servings;
// optional parameters
private int calories = 0;
private int fat = 0;
private int carbohydrate = 0;
private int sodium = 0;
public Builder(int servingSize, int servings) {
this.servingSize = servingSize;
this.servings = servings;
}
public Builder calories(int val)
{ calories = val; return this; }
public Builder fat(int val)
{ fat = val; return this; }
public Builder carbohydrate(int val)
{ carbohydrate = val; return this; }
public Builder sodium(int val)
{ sodium = val; return this; }
public NutritionFacts build() {
return new NutritionFacts(this);
}
}
private NutritionFacts(Builder builder) {
servingSize = builder.servingSize;
servings = builder.servings;
calories = builder.calories;
fat = builder.fat;
soduim = builder.sodium;
carbohydrate = builder.carbohydrate;
}
}
And then use it like this:
NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8).
calories(100).sodium(35).carbohydrate(27).build();
The example above was taken from Effective Java 2
And that doesn't only applies to constructor. Citing Kent Beck in Implementation Patterns:
setOuterBounds(x, y, width, height);
setInnerBounds(x + 2, y + 2, width - 4, height - 4);
Making the rectangle explicit as an object explains the code better:
setOuterBounds(bounds);
setInnerBounds(bounds.expand(-2));