In object-oriented programming, Factory Method Pattern 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.
Factory Method Pattern 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. This is widely used in software development to promote loose coupling.
Let's say we are managing the appearance of a game character. For the sake of familiarity, let's consider the game to be Super Mario. In this game, our objective 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 GameManager class will serve as the core code, which will bring together all the smaller segments.
For example, we have included changeTheme() method for changing themes and operations() method for deciding what to do with various game characters. Note: We introduced 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
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. This factory class is popularly known as the Simple Factory. Now, product instantiation is done by the factory, and the startGame() method is just a means of accessing the instantiated product.
Now, we consider the original problem statement. 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 will 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 would like to have a framework that combines the operations and creation of Mario's character together 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. Afterwards, we'll use the operations() method on the created Mario.
However, there are still a few aspects to consider. 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. As a result, it is decoupled from the concrete subclasses of the MARIO class.
This approach is called as the factory method pattern, where we use createMario() to create an object.
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.
Finally, we create 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 the appropriate concrete creator to the startGame() method of GameManager through which we create the concrete product.
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);
}
}
After discussing many approaches and their drawbacks, we should use the factory method when:
A framework uses abstract classes to define and maintain dependencies between objects; it is also responsible for creating them. In our example, let's say we have a framework that consists of two abstractions, MARIO and MarioMaker. We have to subclass these abstractions to implement their 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.
The factory method pattern encapsulates the responsibility of creating concrete products and moving them out of the framework. Then, subclasses of MarioMaker redefine 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!
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!
Subscribe to get well designed content on data structure and algorithms, machine learning, system design, object orientd programming and math.
©2023 Code Algorithms Pvt. Ltd.
All rights reserved.