Replacing constructors with factory methods
Nov 10, 2015
Design Patterns Good Code

Simply put, a factory method is any method that encapsulates the logic of how instances of a particular class are created. Considering this loose definition, it might seem at first that to a large extent factory methods are little, if any, different from plain old class constructors. As we shall see however, they are a more powerful option that can help us write cleaner and more maintainable code.

The problem

The biggest problem with constructors is that they are not intention revealing. For example, quite often classes can be instantiated in more than one way, perhaps involving different initialization arguments for each variant. Overloaded constructors might work for these cases, however programmers still need to figure which one they are supposed to use which, given that constructor methods cannot be explicitly named in most languages, is not always easy.

As an example, consider a class representing an article review with some simple initialization logic; an article can either be rejected or accepted and if rejected, a reason must be provided.

public class ArticleReview {

  // creates acceptance reviews
  public ArticleReview(Article article, Reviewer reviewer) {
    this(article, reviewer, Response.ACCEPT, null);
  }

  // creates rejection reviews
  public ArticleReview(Article article, Reviewer reviewer, String reason) {
    this(article, reviewer, Response.REJECT, reason);
  }

  private ArticleReview(
      Article article,
      Reviewer reviewer,
      Response response,
      String reason) {

      // ...
  }
}

In this example we define two overloaded class constructors that create acceptance and rejection review instances respectively. In lack of a more descriptive name, their purpose remains obscure unless explicitly declared in documentation, or someone glances at their implementation.

For dynamically typed languages such as Ruby which don’t support constructor overloading at all, more often than not this boils down to cumbersome initialization methods with semi-optional arguments depending on the context. Using the same example as before:

class ArticleReview

  def initialize(article, reviewer, response, reason = nil)
    valid_responses = ["accept", "reject"]
    if !valid_responses.include?(response)
      raise ArgumentError, "Supported responses are #{responses}"
    end
    if response == "reject" && !reason
      raise ArgumentError, "Reason required on rejection"
    end

    # set the instance variables
  end
end

There are several things which a programmer creating instances of this class must know. He needs to to know for example that an article’s review response may either be an acceptance or rejection and which values correspond to each option, or the that the reason parameter is a mandatory argument, applicable only in the case of a rejection. Clearly, all those requirements are not communicated effectively from the initializer method signature alone.

Evidently, the more initialization options are available, the harder it becomes to understand how to create instances of a class correctly and the more likely to misuse it.

Factory methods to the rescue

We can improve our code simply by adding two factory methods on the ArticleReview class:

class ArticleReview

  def initialize(article, reviewer, response, reason = nil)
   # ...
  end

  def self.create_approval_review_for(article, by:)
    new(article, by, 'approve')
  end

  def self.create_rejection_review_for(article, by:, reason:)
    new(article, by, 'reject', reason)
  end
end

This second version of our class communicates explicitly to programmers which class instances are available and which arguments are supported for each variant, describing the domain logic better than the initializer counterpart. Allowing the programmer to choose a well named method rather than a generic constructor, the intent becomes more apparent and consequently the code becomes more readable and less error prone. As a nice side-effect, we can easily search for occurrences where we create acceptance or rejection reviews, which might not be that easy before.

The motivation of the factory methods as described here is to improve code readability by making implicit creational logic explicit by means of intention revealing factory methods. This should not to be confused with the factory method pattern whose motivation is to define an interface to create objects of without having to specify its class.

Conclusion

When there is more than one way to construct an object, you could (and in fact should) consider making its construction logic explicit by using factory methods instead of constructors. Use constructors only when the logic of how instances of a class are created is very explicit and simple.

Share on