Home > Articles > Software Development & Management

Refactoring to Patterns: Creation

Joshua Kerievsky presents six refactorings that 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.
This chapter is from the book

This chapter is from the book

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).

  1. 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.
  2. →Compile and test.

  3. 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.
  4. →Compile and test.

  5. 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].
  6. →Compile and test.

  7. Repeat steps 1–3 for every constructor on the class that you’d like to turn into a Creation Method.
  8. If a constructor on the class has no callers outside the class, make it non-public.
  9. →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.

  1. 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).
  2. 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.

  1. 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:
  2. 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.

  3. Next, I find all callers on the constructor that createTermLoan calls, and I update them to call createTermLoan. For example:
  4. 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.

  5. 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:
  6. 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.

  7. 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:
  8. 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:

  9. 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:
  10. 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.

InformIT Promotional Mailings & Special Offers

I would like to receive exclusive offers and hear about products from InformIT and its family of brands. I can unsubscribe at any time.

Overview


Pearson Education, Inc., 221 River Street, Hoboken, New Jersey 07030, (Pearson) presents this site to provide information about products and services that can be purchased through this site.

This privacy notice provides an overview of our commitment to privacy and describes how we collect, protect, use and share personal information collected through this site. Please note that other Pearson websites and online products and services have their own separate privacy policies.

Collection and Use of Information


To conduct business and deliver products and services, Pearson collects and uses personal information in several ways in connection with this site, including:

Questions and Inquiries

For inquiries and questions, we collect the inquiry or question, together with name, contact details (email address, phone number and mailing address) and any other additional information voluntarily submitted to us through a Contact Us form or an email. We use this information to address the inquiry and respond to the question.

Online Store

For orders and purchases placed through our online store on this site, we collect order details, name, institution name and address (if applicable), email address, phone number, shipping and billing addresses, credit/debit card information, shipping options and any instructions. We use this information to complete transactions, fulfill orders, communicate with individuals placing orders or visiting the online store, and for related purposes.

Surveys

Pearson may offer opportunities to provide feedback or participate in surveys, including surveys evaluating Pearson products, services or sites. Participation is voluntary. Pearson collects information requested in the survey questions and uses the information to evaluate, support, maintain and improve products, services or sites, develop new products and services, conduct educational research and for other purposes specified in the survey.

Contests and Drawings

Occasionally, we may sponsor a contest or drawing. Participation is optional. Pearson collects name, contact information and other information specified on the entry form for the contest or drawing to conduct the contest or drawing. Pearson may collect additional personal information from the winners of a contest or drawing in order to award the prize and for tax reporting purposes, as required by law.

Newsletters

If you have elected to receive email newsletters or promotional mailings and special offers but want to unsubscribe, simply email information@informit.com.

Service Announcements

On rare occasions it is necessary to send out a strictly service related announcement. For instance, if our service is temporarily suspended for maintenance we might send users an email. Generally, users may not opt-out of these communications, though they can deactivate their account information. However, these communications are not promotional in nature.

Customer Service

We communicate with users on a regular basis to provide requested services and in regard to issues relating to their account we reply via email or phone in accordance with the users' wishes when a user submits their information through our Contact Us form.

Other Collection and Use of Information


Application and System Logs

Pearson automatically collects log data to help ensure the delivery, availability and security of this site. Log data may include technical information about how a user or visitor connected to this site, such as browser type, type of computer/device, operating system, internet service provider and IP address. We use this information for support purposes and to monitor the health of the site, identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents and appropriately scale computing resources.

Web Analytics

Pearson may use third party web trend analytical services, including Google Analytics, to collect visitor information, such as IP addresses, browser types, referring pages, pages visited and time spent on a particular site. While these analytical services collect and report information on an anonymous basis, they may use cookies to gather web trend information. The information gathered may enable Pearson (but not the third party web trend services) to link information with application and system log data. Pearson uses this information for system administration and to identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents, appropriately scale computing resources and otherwise support and deliver this site and its services.

Cookies and Related Technologies

This site uses cookies and similar technologies to personalize content, measure traffic patterns, control security, track use and access of information on this site, and provide interest-based messages and advertising. Users can manage and block the use of cookies through their browser. Disabling or blocking certain cookies may limit the functionality of this site.

Do Not Track

This site currently does not respond to Do Not Track signals.

Security


Pearson uses appropriate physical, administrative and technical security measures to protect personal information from unauthorized access, use and disclosure.

Children


This site is not directed to children under the age of 13.

Marketing


Pearson may send or direct marketing communications to users, provided that

  • Pearson will not use personal information collected or processed as a K-12 school service provider for the purpose of directed or targeted advertising.
  • Such marketing is consistent with applicable law and Pearson's legal obligations.
  • Pearson will not knowingly direct or send marketing communications to an individual who has expressed a preference not to receive marketing.
  • Where required by applicable law, express or implied consent to marketing exists and has not been withdrawn.

Pearson may provide personal information to a third party service provider on a restricted basis to provide marketing solely on behalf of Pearson or an affiliate or customer for whom Pearson is a service provider. Marketing preferences may be changed at any time.

Correcting/Updating Personal Information


If a user's personally identifiable information changes (such as your postal address or email address), we provide a way to correct or update that user's personal data provided to us. This can be done on the Account page. If a user no longer desires our service and desires to delete his or her account, please contact us at customer-service@informit.com and we will process the deletion of a user's account.

Choice/Opt-out


Users can always make an informed choice as to whether they should proceed with certain services offered by InformIT. If you choose to remove yourself from our mailing list(s) simply visit the following page and uncheck any communication you no longer want to receive: www.informit.com/u.aspx.

Sale of Personal Information


Pearson does not rent or sell personal information in exchange for any payment of money.

While Pearson does not sell personal information, as defined in Nevada law, Nevada residents may email a request for no sale of their personal information to NevadaDesignatedRequest@pearson.com.

Supplemental Privacy Statement for California Residents


California residents should read our Supplemental privacy statement for California residents in conjunction with this Privacy Notice. The Supplemental privacy statement for California residents explains Pearson's commitment to comply with California law and applies to personal information of California residents collected in connection with this site and the Services.

Sharing and Disclosure


Pearson may disclose personal information, as follows:

  • As required by law.
  • With the consent of the individual (or their parent, if the individual is a minor)
  • In response to a subpoena, court order or legal process, to the extent permitted or required by law
  • To protect the security and safety of individuals, data, assets and systems, consistent with applicable law
  • In connection the sale, joint venture or other transfer of some or all of its company or assets, subject to the provisions of this Privacy Notice
  • To investigate or address actual or suspected fraud or other illegal activities
  • To exercise its legal rights, including enforcement of the Terms of Use for this site or another contract
  • To affiliated Pearson companies and other companies and organizations who perform work for Pearson and are obligated to protect the privacy of personal information consistent with this Privacy Notice
  • To a school, organization, company or government agency, where Pearson collects or processes the personal information in a school setting or on behalf of such organization, company or government agency.

Links


This web site contains links to other sites. Please be aware that we are not responsible for the privacy practices of such other sites. We encourage our users to be aware when they leave our site and to read the privacy statements of each and every web site that collects Personal Information. This privacy statement applies solely to information collected by this web site.

Requests and Contact


Please contact us about this Privacy Notice or if you have any requests or questions relating to the privacy of your personal information.

Changes to this Privacy Notice


We may revise this Privacy Notice through an updated posting. We will identify the effective date of the revision in the posting. Often, updates are made to provide greater clarity or to comply with changes in regulatory requirements. If the updates involve material changes to the collection, protection, use or disclosure of Personal Information, Pearson will provide notice of the change through a conspicuous notice on this site or other appropriate way. Continued use of the site after the effective date of a posted revision evidences acceptance. Please contact us if you have questions or concerns about the Privacy Notice or any objection to any revisions.

Last Update: November 17, 2020