In software engineering, the Factory Method is a creational design pattern that defines an interface or abstract class (base class) for creating an object but lets the subclasses decide which class to instantiate. In other words, this enables a class to delegate the responsibility of object creation to its subclasses. Note: The Factory Method Pattern is also known as Virtual Constructor.
This can be useful when a class doesn't know in advance the exact objects it needs to create or when it wants to delegate the creation of objects to its subclasses for greater flexibility and extensibility. So it is one of the widely used design patterns to promote loose coupling.
Suppose we are managing the appearance of a game character. For the sake of familiarity, let's consider the Super Mario game, where our goal is to manage the instantiation of various versions of Mario characters like Fire Mario, Cape Mario, Invincible Mario, and so on.
First, let's consider a scenario where we have only one type of character in our game: Mario. In this case, we will create a class called Mario and instantiate it directly whenever we need it.
In the following code, we demonstrate how to instantiate Mario in the startGame() method of the GameManager class. Here, the GameManager class will serve as the core code, bringing together all the smaller segments.
For example, we have included the changeTheme() method for changing themes and the operations() method for deciding what to do with various game characters. Note: We added these methods to realize the roles of the GameManager class beyond just instantiating Mario.
class Mario {
public String name() {
// Return Mario's name
}
public void run() {
// Code for Mario to run
}
public void jump() {
// Code for Mario to jump
}
}
//GameManager brings all different parts of the code together
//It is like the heart of the game!
class GameManager {
public void startGame() {
Mario mario = new Mario();
// Operations on Mario
}
public void operations() {
// Code for handling game operations
}
public void changeTheme() {
// Code for changing game theme
}
}
Observations
So the key takeaway from this approach is to delegate the responsibility of creating the object to a separate class. To achieve this, we can create a Factory class whose sole responsibility is to create instances of the Mario class.
class Mario {
// Mario class members and methods
}
// Factory is responsible for creating Mario instances
class Factory {
public Mario createMario() {
return new Mario();
}
}
class GameManager {
// GameManager class members
Factory factory;
public void startGame() {
Mario mario = factory.createMario();
// Operations on Mario
}
public void operations() {
// ...
}
}
Here, we have replaced direct instantiation with instantiation via a factory. So, object instantiation is done by the factory, and the startGame() method is just a means of accessing the instantiated object. This factory class is popularly known as the Simple Factory.
Suppose we require two types of Mario characters: Fire Mario and Cape Mario. For this, we will declare an abstract class (MARIO) for Mario's character, and concrete subclasses to implement different versions of Mario, such as Fire Mario (FireMario) and Cape Mario (CapeMario). These subclasses are concrete products, and the base class MARIO is an abstract product.
// Abstract class for Mario character
abstract class MARIO {
public abstract String name();
public void run() {
// Implementation for the run() method
}
public void jump() {
// Implementation for the jump() method
}
}
// Concrete subclass
class FireMario extends MARIO {
@Override
public String name() {
return "FIRE";
}
}
// Concrete subclass
class CapeMario extends MARIO {
@Override
public String name() {
return "CAPE";
}
}
Again, we can make a startGame() method in the GameManager class to instantiate any one type of Mario based upon some parameter. The GameManager class can look like this:
public class GameManager {
private MARIO mario;
public void startGame(String marioType) {
if (marioType.equals("FIRE")) {
mario = new FireMario();
}
else if (marioType.equals("CAPE")) {
mario = new CapeMario();
}
// Operations on Mario
}
// Rest of the class...
}
Observations
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. Here is the example code:
class FireFactory {
public MARIO createMario() {
return new FireMario();
}
// Operations on Mario
public void operations() {
MARIO mario = createMario();
System.out.println(mario.name());
// Additional operations on Mario
}
}
class CapeFactory {
public MARIO createMario() {
return new CapeMario();
}
// Operations on Mario
public void operations() {
MARIO mario = createMario();
System.out.println(mario.name());
// Additional operations on Mario
}
}
Takeaway: We need a method that combines the operations and creation of Mario's character while still maintaining flexibility!
One key idea is to associate all the factories under a common interface. This interface will have two responsibilities: creating Mario's character and performing essential operations on that character. Let's name this interface (or abstract class) MarioMaker, which will include the methods createMario() and operations().
The critical question is: How does createMario() determine which type of Mario to instantiate? No worries, it won't make that decision, at least not within the interface itself. Here's what we'll do: We'll create subclasses of MarioMaker called FireMaker and CapeMaker, and override the createMario() method to instantiate FireMario and CapeMario, respectively.
Here's the catch: We are delegating the responsibility of creating Mario to the subclasses. Here 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. As a result, it is decoupled from the concrete subclasses of the MARIO class.
Note
Now let's understand the implementation of the above idea using the factory method.
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.
import java.util.Scanner;
// Product interface
interface MARIO {
String name();
void jump();
void run();
}
// Concrete products
class FireMario implements MARIO {
@Override
public String name() {
return "FIRE";
}
@Override
public void jump() {
System.out.println("Fire Mario jumps high!");
}
@Override
public void run() {
System.out.println("Fire Mario runs fast!");
}
}
class CapeMario implements MARIO {
@Override
public String name() {
return "CAPE";
}
@Override
public void jump() {
System.out.println("Cape Mario jumps with a cape glide!");
}
@Override
public void run() {
System.out.println("Cape Mario runs gracefully!");
}
}
// Base Creator class providing factory method and necessary operations
abstract class MarioMaker {
abstract MARIO createMario();
void operations() {
MARIO mario = createMario();
System.out.println(mario.name());
mario.jump();
mario.run();
}
}
// Concrete creators
class FireMaker extends MarioMaker {
@Override
MARIO createMario() {
return new FireMario();
}
}
class CapeMaker extends MarioMaker {
@Override
MARIO createMario() {
return new CapeMario();
}
}
class GameManager {
//works with an instance of concrete creator via base creator
public void startGame(MarioMaker creator) {
creator.operations();
}
//......
}
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("Enter the type of Mario: ");
String input_type = sc.nextLine();
MarioMaker in = null;
GameManager gamer = new GameManager();
if (input_type.equals("Fire")) {
in = new FireMaker();
}
else if (input_type.equals("Cape")) {
in = new CapeMaker();
}
else {
System.out.println("ERROR!");
System.exit(0);
}
gamer.startGame(in);
}
}
We should use the factory method when:
In our example, we have a framework that consists of two abstractions, MARIO and MarioMaker. So we can easily subclass these abstractions to implement character-specific implementations. However, since a particular Mario subclass to instantiate is system-specific, the MarioMaker class doesn't know which concrete product needs to be created.
In other words, the factory method pattern encapsulates the responsibility of creating concrete products, where subclasses of MarioMaker override an abstract method (createMario()) to return the appropriate Mario. Once a MarioMaker subclass is instantiated, it can then instantiate system-specific Mario characters without knowing their class!
Sometimes, we can reuse existing objects instead of rebuilding them. So the factory method does not need to create a new object every time. It can return existing objects by caching them.
Frameworks and libraries often use the Factory Method pattern to provide extensibility and enable clients to create objects without tightly coupling them to specific implementations.
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.
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:
Thanks to Ankit Nishad for his contribution in creating the first version of this content. If you have any queries or feedback, please write us at contact@enjoyalgorithms.com. Enjoy learning, Enjoy oops!