Factory Pattern (A Creational Design Pattern in OOPS)

What is the factory method?

The factory method is a design pattern that allows you to define a base class for creating objects, but lets subclasses specify which class to instantiate. Essentially, the factory method enables a class to delegate object creation to its subclasses.

Problem statement

Let's say we are managing the appearance of a game character; for familiarity, let the game be Super Mario. We'll be only managing the different versions of Mario, e.g., Fire Mario, Cape Mario, Invincible Mario, etc. Our primary goal is to instantiate Mario's character.

Case 1:  When a single type is present

First, let us consider that we have only one type of Mario's character, i.e., Mario. So, our approach will be to make a class Mario and instantiate it directly wherever needed. In the following code, we have considered instantiation in the StartGame() method of GameManager class only but, it is possible that we have to instantiate Mario in many parts of the code.

Note: The GameManager class is the core part of the code where all smaller segments come together. For instance, we've added the ChangeTheme() method, which helps change themes, and the Operations() method for deciding operations on various game characters. (These methods are introduced to realize the role of GameManager class, i.e., it is dealing with a lot of other things too).

Observations

  • In this approach, the GameManager class has the responsibilities like creating Mario, deciding operations via Operations(), changing theme via ChangeTheme(), etc. This suggests that the GameManager class has more than one responsibility, due to which this class violates the Single Responsibility Principle (SRP).
  • Also, we may need an instance of Mario class in many parts of the application other than the GameManager class. All such parts will have to instantiate Mario class directly with the "new" keyword and hence will get coupled to the Mario class. If the Mario class is modified in future modification, then all classes that directly instantiated Mario might get affected, which is not desirable (i.e., less coupling is preferred).

The takeaway from this approach is: Move the responsibility of creating the object (here object of Mario class) to a separate class, say Factory. In other words, We create a Factory class whose only responsibility is to create an instance of Mario class. Now, our code will be:

Here, we replaced direct instantiation with instantiation via a factory. This factory class is popularly known as Simple Factory. Now, product instantiation is done by the factory, and the StartGame() method is just a means of accessing the instantiated product.

Case 2: When multiple types are present

Now, consider the original problem statement, which required two types of Mario characters, namely, Fire and Cape Mario.

For this, we will declare an abstract class (MARIO) of Mario's character, and its concrete subclasses will implement different versions of Mario like Fire Mario (FireMario) and Cape Mario (CapeMario). These subclasses are concrete products, and the base class is an abstract product.

Again, we can make a StartGame() method in the GameManager class to instantiate any one type of Mario based upon some parameter (system-specific). The GameManager class will look like

Observations

  • As in the previous case, the GameManager class violates SRP as it is responsible for creating all types of Mario and has other methods/responsibilities.
  • The addition of new types of Mario's character will result in a more complex, hard to maintain, and much more coupled GameManager class. For instance, say we are introducing Cat Mario (a new type of Mario), then we've to change the GameManager class accordingly; this suggests that GameManager is not closed for modification. Therefore, it violates the Open-Closed principle.

We've seen that introducing a Factory in such code helps to some extent. So, one might think of making different factories for each type of Mario and then creating the corresponding type of Mario. But if we need to perform specific operations on the Mario character, which has been created in the factory, then we have to write the same code in each factory. E.g.

  • The StartGame() in GameManager class has to use one factory among these factories, which will again make it riddled with if-else and less flexible as it will be coupled with the Factories this time.
  • Also, if each factory is supposed to do the same operations on the Mario it creates, our code is not reusable as we define the same operations in many classes (factories).

Takeaway: So, we'd like to have a framework that relates operations and the creation of Mario's character together yet still maintains flexibility!

Final approach

One key idea is to relate all the factories under some interface (or base class). That interface will have two responsibilities: creating Mario's character and essential operations on that character. Let's call this interface (or abstract class) as MarioMaker, which will have the methods CreateMario() and Operations() for necessary operations.

But the question is how CreateMario() decides which type of Mario to instantiate? No worries, it won't decide, at least in the interface itself. What we'll do is we'll subclass MarioMaker into FireMaker and CapeMaker and override the CreateMario() to instantiate FireMario and CapeMario, respectively.

Here's the catch: We are delegating the responsibility of creation of Mario to the subclasses. And then we’ll use the Operations() on the created Mario.

There are still quite a few aspects of this approach. The Operations() method will use an object of type MARIO (which is abstract), due to which this method does not know about the type of Mario it uses; hence it is decoupled from the concrete subclasses of MARIO class.

Takeaways from this approach:

  • We will be using interfaces and abstract classes in the GameManager class to instantiate the character, which is good because
    writing code to interfaces/abstract classes is better than using concrete classes directly. (Think!)
  • In the future, adding more types of MARIO characters will be easy because we will not be modifying the GameManager class but adding new subclasses of MARIO and MarioMaker classes. This suggests our GameManager class now follows the Open-Closed principle.
  • The code is now more reusable in terms of the Operations() method.
  • The GameManager class is not directly coupled with the concrete classes.

The final approach that we just discussed is the factory method, where CreateMario() is used to manufacture an object. 

Components and Structure

  1. Product (MARIO)
    Provides interface of the object the factory method creates.
  2. Concrete Products (FireMario, CapeMario)
    Sub-classes of product interface provide an implementation of each type of product.
  3. Creator (MarioMaker)
    Declares the factory method, which returns an object of Product class and operations to be done on the product returned by the factory method.

    Note: The Creator needs not to be a purely abstract class or interface every time! We can give some default implementation to the factory method in the Creator class itself that returns a default Concrete Product that the subclasses can further override.

    Also, the Creator class is not only responsible for creating products, but it has some operations to do on the product created too. The factory method decouples the operations from the concrete products, i.e., the operations don't know the type of Mario on which they are working!*

  4. Concrete Creators (FireMaker, CapeMaker)
    Overrides the base factory method to return an instance of one of the concrete products. Note: Although the concrete creators instantiate concrete products, the return type of the factory method is set as the Product.

Implementation

We've already seen the problem and its inefficient solutions. So let's go straight into a quick solution using the factory method.

Solution Approach

We create the product interface — MARIO. Then, we implement its subclasses: concrete products — FireMario and CapeMario.

Now, we create the creator class —MarioMaker which declares a factory method(CreateMario()) for creating concrete products and operational methods (Operations()) to work on the created products. Again note that we can give default implementation of CreateMario() in MarioMaker itself, or we can leave it as a completely abstract method which implies that MarioMaker does not need to be an interface always; it can be a base class or an abstract class too.

One important point is that this pattern will work when the base creator class (here, the MarioMaker class) works with the product interface (MARIO). By doing so, the methods in MarioMaker do not depend on the concrete products.

Finally, we create the GameManager class that will use MarioMaker and MARIO classes only, i.e., base class and/or interfaces only. Based on the system requirements, we pass appropriate concrete creator to the StartGame() method of GameManager through which we create the concrete product.

Complete solution code in C++

Complete solution code in Java

When to use Factory Method?

After discussing many approaches and their drawbacks, we can say that we should use the factory method when 

  • we don't know the exact types of objects to be created beforehand.
  • We want to extend the internal components of a framework or library.

    A framework uses abstract classes to define and maintain dependencies between objects; it is also responsible for creating them. In our example of Mario's character, say we have a framework that consists of two abstractions, MARIO and MarioMaker. As clients, we have to subclass these abstractions to implement their character-specific implementations. But since a particular Mario subclass to instantiate is system-specific, the MarioMaker class doesn't know which concrete product needs to be created. 

    The factory method pattern encapsulates the responsibility of creating concrete products and moves them out of the framework. Then, subclasses of MarioMaker redefine an abstract method (*CreateMario()) to return appropriate Mario. Once a MarioMaker* subclass is instantiated, it can then instantiate system-specific Mario characters without knowing their class!
  • We want to reuse existing objects instead of rebuilding them. Notice that the factory method does not need to create a new object every time. It can return existing objects from some cache or any source.

Consequences

  • The factory method eliminates the need to couple concrete products to the Client code as it only deals with the product interface.
  • As we've already seen, the factory method supports the Single Responsibility Principle and the Open-Closed Principle.
  • The factory method connects parallel class hierarchies, which occur when a class delegates some of its responsibilities to a separate class. It establishes the connection between the two hierarchies (here, the two hierarchies are MarioMaker and MARIO, which parallel each other in structures).
  • Clients might need to subclass the Creator class to create a single concrete product. Although this is not a substantial disadvantage, it still adds another evolution in the code for a single concrete product.

Application of Factory Method

Some popular uses of factory method creational design pattern can be found

Conclusion

Factory method pattern is a creational design pattern that should be used when a class can't anticipate the class of objects it must create. 

  • First, we saw the simple factory, where we realized that we should make a separate class for instantiation of products (even if only one variant of product exists). 
  • Then we explored the scenario of multiple variants of a product. We saw that using different simple factories for each product variant is neither a flexible nor efficient approach.
  •  Finally, we formulated the factory method pattern where we created a base creator class and delegated the responsibility of product creation to its sub-classes.

We also saw different implementations of factory methods, for instance — an abstract factory method non-abstract factory method. A parameterized factory method is also a possibility. (Explore!)
Our discussion shows that using the factory method under appropriate circumstances results in a flexible, easy-to-maintain, and reliable code that follows various Object-Oriented design principles.

Further Exploration:

  • Making parameterized factory methods!
  • When should we use an abstract creator class with a default implementation of the factory method?

Enjoy learning, Enjoy oops!

More From EnjoyAlgorithms

© 2022 Code Algorithms Pvt. Ltd.

All rights reserved.