Design Patterns Series: Factory Method

Explore the use cases and advantages of incorporating a factory pattern in your applications.

A common problem software developers run into is the requirement of building various subclasses based on some type of parameter. An example of this is shown below. In this snippet we are designing a game with a super class named character, that can have multiple different character types. Depending on the type of character we desire, we can pass a string specifying which object to produce.

public class Game {
  public Character createCharacter(String characterType) {
    if (characterType.equals("warrior")) {
              return new Warrior();
          } else if (characterType.equals("mage")) {
              return new Mage();
          } else if (characterType.equals("archer")) {
              return new Archer();
          }
          throw new CharacterNotFoundException();
    }
  }
    
    public static void main(String[] args) {
        Game game = new Game();
        Character playerCharacter = game.createCharacter("warrior");
        playerCharacter.attack();
    }    
}

Here the createCharacter method takes a character type as an argument and directly creates a specific character class. This is a simple and convenient solution, but has the problem of tightly coupling the concrete character classes to our Game class. This makes it difficult to expand upon our existing character types without modifying the Game class as well.

Solution

In order to decouple our game class from the createCharacter factory, we can implement the Factory Method pattern. This will help add flexibility to our code base. In the solution below we have an interface called CharacterFactory that has a single method for character creation. Now we can include various implementations of the CharacterFactory and allowing subclasses to alter the type of objects being created.

interface CharacterFactory {
    Character createCharacter();
}

class WarriorFactory implements CharacterFactory {
    @Override
    public Character createCharacter() {
        return new Warrior();
    }
}

class MageFactory implements CharacterFactory {
    @Override
    public Character createCharacter() {
        return new Mage();
    }
}

class ArcherFactory implements CharacterFactory {
    @Override
    public Character createCharacter() {
        return new Archer();
    }
}

public class Game {
    public Character createCharacter(CharacterFactory factory) {
        return factory.createCharacter();
    }
    
    public static void main(String[] args) {
        Game game = new Game();
        CharacterFactory factory = new WarriorFactory();
        Character playerCharacter = game.createCharacter(factory);
        playerCharacter.attack();
    }
}

Using this approach we can now decouple the Game class from the character creation factory by allow various types of factories to be passed in. Now you can add character types by implementing additional factories and you can create custom specifications for object creation inside of these factories.

Key Components

  1. Creator: This is an interface that declares a type of Factory Method. It can also define additional methods operate upon the objects that get created.
  2. ConcreteCreator: These are the actual implementation of the Creator interface that are responsible for creating different types of products.
  3. Product: An interface or abstract class that acts as a base class for objects being created.
  4. ConcreteProduct: The concrete classes that implement the Product interface.

Benefits

  • Encapsulates the object creation process.
  • Allows flexibility in choosing which classes to instantiate.
  • Supports the Open-Closed principle, allowing new product types to be adding without modifying existing code.

Conclusion

The Factory Method is a creational design pattern in software engineering that provides an interface for creating objects but allows subclasses to alter the type of objects that will be created. It enhances the flexibility of your application and decouples the object creation process from the class that requires the object.