Refactoring to Patterns: Creation
- Replace Constructors with Creation Methods
- Move Creation Knowledge to Factory
- Encapsulate Classes with Factory
- Introduce Polymorphic Creation with Factory Method
- Encapsulate Composite with Builder
- Inline Singleton
While every object-oriented system creates objects or object structures, the creation code is not always free of duplication, simple, intuitive, or as loosely coupled to client code as it could be. The six refactorings in this chapter target design problems in everything from constructors to overly complicated construction logic to unnecessary Singletons [DP]. While these refactorings don’t address every creational design problem you’ll likely encounter, they do address some of the most common problems.
If there are too many constructors on a class, clients will have a difficult time knowing which constructor to call. One solution is to reduce the number of constructors by applying such refactorings as Extract Class [F] or Extract Subclass [F]. If that isn’t possible or useful, you can clarify the intention of the constructors by applying Replace Constructors with Creation Methods (57).
What is a Creation Method? It is simply a static or nonstatic method that creates and returns an object instance. For this book, I decided to define the Creation Method pattern to help distinguish it from the Factory Method [DP] pattern. A Factory Method is useful for polymorphic creation. Unlike a Creation Method, a Factory Method may not be static and must be implemented by at least two classes, typically a superclass and a subclass. If classes in a hierarchy implement a method similarly, except for an object creation step, you can likely remove duplicate code by first applying Introduce Polymorphic Creation with Factory Method (88).
A class that is a Factory is one that implements one or more Creation Methods. If data and/or code used in object creation become sprawled across numerous classes, you’ll likely find yourself frequently updating code in numerous places, a sure sign of the smell Solution Sprawl (43). Applying Move Creation Knowledge to Factory (68) will reduce creational sprawl by consolidating creation code and data under a single Factory.
Encapsulate Classes with Factory (80) is another useful refactoring that involves the Factory pattern. The two most common motivations for applying this refactoring are (1) to ensure that clients communicate with instances of classes via a common interface and (2) to reduce client knowledge of classes while making instances of the classes accessible via a Factory.
To simplify the construction of an object structure, there is no better pattern than Builder [DP]. Encapsulate Composite with Builder (96) shows how a Builder can provide a simpler, less error-prone way to construct a Composite [DP].
The final refactoring in this section is Inline Singleton (114). It was a joy to write this refactoring, for I often encounter Singletons that do not need to exist. This refactoring, which shows you how to remove a Singleton from your code, features advice about Singletons from Ward Cunningham, Kent Beck, and Martin Fowler.
Replace Constructors with Creation Methods
Constructors on a class make it hard to decide which constructor to call during development.
Replace the constructors with intention-revealing Creation Methods that return object instances.
Motivation
Some languages allow you to name constructors any way you like, regardless of the name of the class. Other languages, such as Java and C++, don’t allow this; each constructor must be named after its class. If you have one simple constructor, this may not be a problem. On the other hand, if you have multiple constructors, programmers will have to choose which constructor to call by studying the expected parameters and/or poking around at the constructor code. What’s wrong with that? A lot.
Constructors simply don’t communicate intention efficiently or effectively. The more constructors you have, the easier it is for programmers to choose the wrong one. Having to choose which constructor to call slows down development, and the code that does call one of the many constructors often fails to sufficiently communicate the nature of the object being constructed.
If you need to add a new constructor to a class with the same signature as an existing constructor, you’re out of luck. Because they have to share the same name, you can’t add the new constructor—since it isn’t possible to have two constructors with the same signature in the same class, despite the fact that they would create different kinds of objects.
It’s common, particularly on mature systems, to find numerous constructors that are no longer being used yet continue to live on in the code. Why are these dead constructors still present? Most of the time it’s because programmers don’t know that the constructors have no caller. Either they haven’t checked for callers (perhaps because the search expression they’d need to formulate is too complicated) or they aren’t using a development environment that automatically highlights uncalled code. Whatever the reason, dead constructors only bloat a class and make it more complicated than it needs to be.
A Creation Method can help make these problems go away. A Creation Method is simply a static or nonstatic method on a class that instantiates new instances of the class. There are no name constraints on Creation Methods, so you can name them to clearly express what you are creating (e.g., createTermLoan() or createRevolver()). This naming flexibility means that two differently named Creation Methods can accept the same number and type of arguments. And for programmers who lack modern development environments, it’s usually easier to find dead Creation Method code than it is to find dead constructor code because the search expressions on specifically named methods are easier to formulate than the search expressions on one of a group of constructors.
One liability of this refactoring is that it may introduce a nonstandard way to perform creation. If most of your classes are instantiated using new yet some are instantiated using a Creation Method, programmers will have to learn how creation gets done for each class. However, this nonstandard technique for creation may be a lesser evil than having classes with too many constructors.
After you have identified a class that has many constructors, it’s best to consider applying Extract Class [F] or Extract Subclass [F] before you decide to apply this refactoring. Extract Class is a good choice if the class in question is simply doing too much work (i.e., it has too many responsibilities). Extract Subclass is a good choice if instances of the class use only a small portion of the class’s instance variables.
Mechanics
Before beginning this refactoring, identify the catch-all constructor, a full-featured constructor to which other constructors delegate their work. If you don’t have a catch-all constructor, create one by applying Chain Constructors (340).
- Find a client that calls a class’s constructor in order to create a kind of instance. Apply Extract Method [F] on the constructor call to produce a public, static method. This new method is a creation method. Now apply Move Method [F] to move the creation method to the class containing the chosen constructor.
- Find all callers of the chosen constructor that instantiate the same kind of instance as the creation method and update them to call the creation method.
- If the chosen constructor is chained to another constructor, make the creation method call the chained constructor instead of the chosen constructor. You can do this by inlining the constructor, a refactoring that resembles Inline Method [F].
- Repeat steps 1–3 for every constructor on the class that you’d like to turn into a Creation Method.
- If a constructor on the class has no callers outside the class, make it non-public.
→Compile and test.
→Compile and test.
→Compile and test.
→Compile.
Example
This example is inspired from the banking domain and a certain loan risk calculator I spent several years writing, extending, and maintaining. The Loan class had numerous constructors, as shown in the following code.
public class Loan... public Loan(double commitment, int riskRating, Date maturity) { this(commitment, 0.00, riskRating, maturity, null); } public Loan(double commitment, int riskRating, Date maturity, Date expiry) { this(commitment, 0.00, riskRating, maturity, expiry); }
public Loan(double commitment, double outstanding, int customerRating, Date maturity, Date expiry) { this(null, commitment, outstanding, customerRating, maturity, expiry); } public Loan(CapitalStrategy capitalStrategy, double commitment, int riskRating, Date maturity, Date expiry) { this(capitalStrategy, commitment, 0.00, riskRating, maturity, expiry); } public Loan(CapitalStrategy capitalStrategy, double commitment, double outstanding, int riskRating, Date maturity, Date expiry) { this.commitment = commitment; this.outstanding = outstanding; this.riskRating = riskRating; this.maturity = maturity; this.expiry = expiry; this.capitalStrategy = capitalStrategy; if (capitalStrategy == null) { if (expiry == null) this.capitalStrategy = new CapitalStrategyTermLoan(); else if (maturity == null) this.capitalStrategy = new CapitalStrategyRevolver(); else this.capitalStrategy = new CapitalStrategyRCTL(); } }
Loan could be used to represent seven kinds of loans. I will discuss only three of them here. A term loan is a loan that must be fully paid by its maturity date. A revolver, which is like a credit card, is a loan that signifies “revolving credit”: you have a spending limit and an expiry date. A revolving credit term loan (RCTL) is a revolver that transforms into a term loan when the revolver expires.
Given that the calculator supported seven kinds of loans, you might wonder why Loan wasn’t an abstract superclass with a subclass for each kind of loan. After all, that would have cut down on the number of constructors needed for Loan and its subclasses. There were two reasons why this was not a good idea.
- What distinguishes the different kinds of loans is not so much their fields but how numbers, like capital, income, and duration, are calculated. To support three different ways to calculate capital for a term loan, we wouldn’t want to create three different subclasses of Loan. It’s easier to support one Loan class and have three different Strategy classes for a term loan (see the example from Replace Conditional Logic with Strategy, 129).
- The application that used Loan instances needed to transform loans from one kind of loan to another. This transformation was easier to do when it involved changing a few fields on a single Loan instance, rather than completely changing one instance of a Loan subclass into another.
If you look at the Loan source code presented earlier, you’ll see that it has five constructors, the last of which is its catch-all constructor (see Chain Constructors, 340). Without specialized knowledge, it is difficult to know which constructors create term loans, which ones create revolvers, and which ones create RCTLs.
I happen to know that an RCTL needs both an expiry date and a maturity date, so I know that to create an RCTL, I must call a constructor that lets me pass in both dates. Did you know that? Do you think the next programmer who reads this code will know it?
What else is embedded as implicit knowledge in the Loan constructors? Plenty. If you call the first constructor, which takes three parameters, you’ll get back a term loan. But if you want a revolver, you’ll need to call one of the constructors that take two dates and then supply null for the maturity date. I wonder if all users of this code will know this? Or will they just have to learn by encountering some ugly defects?
Let’s see what happens when I apply the Replace Constructors with Creation Methods refactoring.
- My first step is to find a client that calls one of Loan’s constructors. Here is one such caller that resides in a test case:
- Next, I find all callers on the constructor that createTermLoan calls, and I update them to call createTermLoan. For example:
- The createTermLoan method is now the only caller on the constructor. Because this constructor is chained to another constructor, I can remove it by applying Inline Method [F] (which, in this case, is actually “inline constructor”). This leads to the following changes:
- Now I repeat steps 1–3 to produce additional creation methods on Loan. For example, here is some code that calls Loan’s catch-all constructor:
- The last step is to change the visibility of the only remaining public constructor, which happens to be Loan’s catch-all constructor. Since it has no subclasses and it now has no external callers, I make it private:
public class CapitalCalculationTests... public void testTermLoanNoPayments() { ... Loan termLoan = new Loan(commitment, riskRating, maturity); ... }
In this case, a call to the above Loan constructor produces a term loan. I apply Extract Method [F] on that call to produce a public, static method called createTermLoan:
public class CapitalCalculationTests... public void testTermLoanNoPayments() { ... Loan termLoan = createTermLoan(commitment, riskRating, maturity); ... } public static Loan createTermLoan(double commitment, int riskRating, Date maturity) { return new Loan(commitment, riskRating, maturity); }
Next, I apply Move Method [F] on the creation method, createTermLoan, to move it to Loan. This produces the following changes:
public class Loan... public static Loan createTermLoan(double commitment, int riskRating, Date maturity) { return new Loan(commitment, riskRating, maturity); } public class CapitalCalculationTest... public void testTermLoanNoPayments() { ... Loan termLoan = Loan.createTermLoan(commitment, riskRating, maturity); ... }
I compile and test to confirm that everything works.
public class CapitalCalculationTest... public void testTermLoanOnePayment() { ...Loan termLoan = new Loan(commitment, riskRating, maturity);Loan termLoan = Loan.createTermLoan(commitment, riskRating, maturity);... }
Once again, I compile and test to confirm that everything is working.
public class Loan...public Loan(double commitment, int riskRating, Date maturity) {this(commitment, 0.00, riskRating, maturity, null);}public static Loan createTermLoan(double commitment, int riskRating, Date maturity) { return new Loan(commitment, 0.00, riskRating, maturity, null); }
I compile and test to confirm that the change works.
public class CapitalCalculationTest... public void testTermLoanWithRiskAdjustedCapitalStrategy() { ... Loan termLoan = new Loan(riskAdjustedCapitalStrategy, commitment, outstanding, riskRating, maturity, null); ... }
Notice the null value that is passed in as the last parameter to the constructor. Passing in null values to a constructor is bad practice. It reduces the code’s readability. It usually happens because programmers can’t find the exact constructor they need, so instead of creating yet another constructor they call a more general-purpose one.
To refactor this code to use a creation method, I’ll follow steps 1 and 2. Step 1 leads to another createTermLoan method on Loan:
public class CapitalCalculationTest... public void testTermLoanWithRiskAdjustedCapitalStrategy() { ... Loan termLoan = Loan.createTermLoan(riskAdjustedCapitalStrategy, commitment, outstanding, riskRating, maturity, null); ... } public class Loan... public static Loan createTermLoan(double commitment, int riskRating, Date maturity) { return new Loan(commitment, 0.00, riskRating, maturity, null); } public static Loan createTermLoan(CapitalStrategy riskAdjustedCapitalStrategy, double commitment, double outstanding, int riskRating, Date maturity) { return new Loan(riskAdjustedCapitalStrategy, commitment, outstanding, riskRating, maturity, null); }
Why did I choose to overload createTermLoan(ノ) instead of producing a creation method with a unique name, like createTermLoanWithStrategy(ノ)? Because I felt that the presence of the CapitalStrategy parameter sufficiently communicated the difference between the two overloaded versions of createTermLoan(ノ).
Now for step 2 of the refactoring. Because the new createTermLoan(ノ) calls Loan’s catch-all constructor, I must find other clients that call the catch-all constructor to instantiate the same kind of Loan produced by createTermLoan(ノ). This requires careful work because some callers of the catch-all constructor produce revolver or RCTL instances of Loan. So I update only the client code that produces term loan instances of Loan.
I don’t have to perform any work for step 3 because the catch-all constructor isn’t chained to any other constructors. I continue to implement step 4, which involves repeating steps 1–3. When I’m done, I end up with the following creation methods:
public class Loan... private Loan(CapitalStrategy capitalStrategy, double commitment, double outstanding, int riskRating, Date maturity, Date expiry)...
I compile to confirm that everything still works. The refactoring is complete.
It’s now clear how to obtain different kinds of Loan instances. The ambiguities have been revealed and the implicit knowledge has been made explicit. What’s left to do? Well, because the creation methods take a fairly large number of parameters, it may make sense to apply Introduce Parameter Object [F].
Variations
Parameterized Creation Methods
As you consider implementing Replace Constructors with Creation Methods, you may calculate in your head that you’d need something on the order of 50 Creation Methods to account for every object configuration supported by your class. Writing 50 methods doesn’t sound like much fun, so you may decide not to apply this refactoring. Keep in mind that there are other ways to handle this situation. First, you need not produce a Creation Method for every object configuration: you can write Creation Methods for the most popular configurations and leave some public constructors around to handle the rest of the cases. It also makes sense to consider using parameters to cut down on the number of Creation Methods.
Extract Factory
Can too many Creation Methods on a class obscure its primary responsibility? This is really a matter of taste. Some folks find that when object creation begins to dominate the public interface of a class, the class no longer strongly communicates its main purpose. If you’re working with a class that has Creation Methods on it and you find that the Creation Methods distract you from the primary responsibilities of the class, you can refactor the related Creation Methods to a single Factory, like so:
It’s worth noting that LoanFactory is not an Abstract Factory [DP]. Abstract Factories can be substituted at runtime—you can define different Abstract Factory classes, each of which knows how to return a family of products, and you can outfit a system or client with a particular Abstract Factory instance. Factories tend to be less sophisticated. They are often implemented as a single class that is not part of any hierarchy.