The Telescoping Constructor (Anti-Pattern)
The Telescoping Constructor
Widget(varA) {...}
Widget(varA, varB) {...}
Widget(varA, varB, varC) {...}
or
Widget widget = new Widget(x);
widget.setVarA(...)
widget.setVarB(...)
widget.setVarC(...)
There are both shared and specific problems for each variety. Let's talk about both.
The 1st case, supernumerary constructors.
What's wrong with this?
Well, the good news is you provide quiet consumption since there's a bespoke constructor for every use case. Rather than have noisy construction, where the consumer must fill in a bunch of nulls, you move the noise into the implementation. We shouldn't make assumptions on how many times the class will be used, so keeping the complexity to ourselves is generous and will lead to a more maintainable macro system.
However, one problem here is that the end consumer is not restricted in their choice of constructors. Like shopping at the super market, sometimes the variety of choice can be oppressive. In order to decide, (s)he'll have to study and understand the internal workings of the class. It's no longer a black box.
In Japanese there's a saying, "大は小を兼ねる" or "Big substitutes for small." A wide constructor acts as a Wicket Gate for whatever wants to come in. Sometimes this may be a convenience. Sometimes it may open up the castle to invaders
The 2nd case, mutators used for construction.What's wrong with this? |
Identifying this case may be more subtle, in that the mutators may not always be adjacent to the constructor.
Once you've crossed this line it may not be apparent that you even have a problem.
We should strive to use the tools the language provides to limit the possible states to those dictated by our requirements. Our class may be created, run some logic internally, and spit out a result. This type of class should not allow state change from the outside. Doing so means the consumer will have to understand the internals of the implementation to safely operate. Otherwise, in the middle of execution (s)he could throw a monkey wrench into the works.
Another problem is that if our class is operating in a multi-threaded environment and we're not careful about explicitly blocking state, we may be producing unexpected, erroneous output. Offering mutators passes the responsibility for those decisions somewhat to the consumer. Again, our class is no longer a black box.
First (and this goes for any class outside of the scope of this conversation) limit the use of mutators. Think carefully before introducing one. Does this actually need to change state post-construction? Must the change be externally introduced?
Parsimonious use of mutators makes a class a quick grock.
Further consideration
To the degree possible make member variables final. This is a clear expression that per instance, they should have one and only one state. I'd go as far as to say that the default state of variables could be final and have the language require an explicit keyword to open them up rather than to restrict them. This would force the programmer to pause to consider issues such as we're discussing here.
Liberal use of final makes a class a quick grock.
Liberal use of final makes a class a quick grock.
The canonical solution for the Telescoping Constructor anti-pattern is to employ a Builder pattern. While any chance to use a new pattern will arm you for next interview, let's pause to consider whether the Builder is really right for you first.
If you need a constructor for every possible null/non-null state permutation of your member variables, your class may be bloated and/or violating single-responsibility. First revisit what's gotten you here. Possibly this is a red flag for a tech debt story to clean up after a past expedient.
Sometimes you may see a case where the accessors from a single object are used exclusively to populate the constructor in question. In this case, can you just pass the object itself as a single argument into the constructor?
As this expands into N+ objects, you may want to consider whether you have a "Commuting by Winnebago" problem. You're not sure what you're going to need for the job so you bring the whole house along just in case. Can you split the class up? Is the class your constructing describing a single concept? Frequently this can also be stated, "Does this class have an equivalent representation in the data layer?"
If not, you may have a "Nonce class." Single responsibility is violated not only by multiple responsibility but also by no-responsibility. If the thing you're building doesn't really do anything, something's wrong.
Optional
Java 8 introduces Optional. This can help us eliminate some of the ambiguity in a wide constructor. If we reduce the number of our constructors and wrap the non-mandatory variables in Optional, we move in the blackish direction, offering more information to the consumer about our box.
Comments
Post a Comment