Definition
This is another exemplary creational design pattern. It advocates delegating the responsibility of creating required objects to subclasses, rather than handling it directly.
If this concept seems unclear, continue reading; it will be perfectly comprehensible by the end.
Idea Behind Factory Method
[Note: Please open the image in a new tab for better visualization]
Transform Figure 01 to Figure 02, How?
Avoid branching (if-else)
Delegate object creation to subclasses
Each subclass is responsible for a single object creation logic
Towards Factory Method - Step by Step
Step 01: Start a Logistic Service
We want to start our logistics business (
Logistic
class)- Initially, we consider two services: road logistic and sea logistic
We need transportation (
Transport
class)
class Transport {
private String name;
public Transport(String name) {
this.name = name;
}
public void delivery() {
String message = "Delivery not available!";
if (name.equals("Truck"))
message = "On road delivery by " + name;
else if (name.equals("Ship")) message = "On sea delivery by " + name;
System.out.println(message);
}
}
class Logistic {
private Transport transport;
public void planDelivery(String type) {
String name = "NA";
if (type.equals("ROAD")) name = "Truck";
else if (type.equals("SEA")) name = "Ship";
transport = new Transport(name);
transport.delivery();
}
}
class FactoryMethodDemo {
public static void main(String[] args) {
Logistic logistic = new Logistic();
logistic.planDelivery("ROAD");
logistic.planDelivery("SEA");
}
}
The main problems with the implementations are the red parts mentioned in ❶ & ❷ in the above picture.
When we write code using if-else:
these are not extensible
not closed for modification
also this indicates the class has multiple responsibilities
Let’s solve ❶ & ❷ step by step.
Step 02: Make Transport Flexible
Inside Transport
class, we have delivery()
method as follows:
public void delivery() {
String message = "Delivery not available!";
if (name.equals("Truck"))
message = "On road delivery by " + name;
else if (name.equals("Ship")) message = "On sea delivery by " + name;
System.out.println(message);
}
We can create subclasses for each responsibility and delegate the functionalities to the subclasses.
interface Transport {
void delivery();
}
class Truck implements Transport {
private String name = "Truck";
@Override
public void delivery() {
String message = "On road delivery by " + name;
System.out.println(message);
}
}
class Ship implements Transport {
private String name = "Ship";
@Override
public void delivery() {
String message = "On sea delivery by " + name;
System.out.println(message);
}
}
class Logistic {
private Transport transport;
public void planDelivery(String type) {
if (type.equals("ROAD"))
transport = new Truck();
else transport = new Ship();
transport.delivery();
}
}
public class FactoryMethodDemo {
public static void main(String[] args) {
Logistic logistic = new Logistic();
logistic.planDelivery("ROAD");
logistic.planDelivery("SEA");
}
}
Now we have two separate Transport
implementations - Truck
& Ship
. We delegate our delivery
logic to the subclasses.
This is how we can avoid using if-else (the beauty of Inheritance in OOP 😊)!
As we instantiate specific classes (Truck and Ship) from planDelivery()
in Logistic
, we don’t need if-else inside Transport
class anymore!
public void planDelivery(String type) {
if (type.equals("ROAD"))
transport = new Truck();
else transport = new Ship();
transport.delivery();
}
Step 03: The Main Part - Factory Method
Though we removed the non-flexible part from the Transport
class we still have the same in the Logistic
class.
Transport object creation is happening conditionally! So this violates:
SRP (Single Responsibility Principle)
OCP (Open Closed Principle)
We know, how we can use inheritance to break the if-else statements. Let’s apply the same here.
Two extended Logistic classes are created:
RoadLogistic
&SeaLogistic
Introduced
createTransport()
abstract method to delegate object creation logic into subclasses - this is the main part of the Factory Method Design PatternplanDelivery()
doesn’t depend ontype
anymore
interface Transport {
void delivery();
}
class Truck implements Transport {
private String name = "Truck";
@Override
public void delivery() {
String message = "On road delivery by " + name;
System.out.println(message);
}
}
class Ship implements Transport {
private String name = "Ship";
@Override
public void delivery() {
String message = "On sear delivery by " + name;
System.out.println(message);
}
}
abstract class Logistic {
private Transport transport;
public abstract Transport createTransport();
public void planDelivery() {
transport = createTransport();
transport.delivery();
}
}
class RoadLogistic extends Logistic {
@Override
public Transport createTransport() {
return new Truck();
}
}
class SeaLogistic extends Logistic {
@Override
public Transport createTransport() {
return new Ship();
}
}
class FactoryMethodDemo {
public static void main(String[] args) {
Logistic roadLogistic = new RoadLogistic();
roadLogistic.planDelivery();
Logistic seaLogistic = new SeaLogistic();
seaLogistic.planDelivery();
}
}
Benefits
So from a design perspective, if-else is a bottleneck in our code. We remove the limitation by applying Inheritance effectively in our code.
The main goals of the Factory Method Desing Pattern are:
Ensure SRP (Single Responsibility Principle)
Ensure OCP (Open Closed Principle)
Observations
These are some observations you should keep in mind from the high level, otherwise, you will not be able to feel the Factory Method Design Pattern!
Factory Method Design Pattern says,
Suppose
ClassB
has multiple subclasses,ClassB1
,ClassB2
, … …,ClassBn
If
ClassA
HAS-AClassB
object, don’t handleClassB
subclass creation logic insideClassA
this will force you to put if-else condition
if-else is not extensible (requires further modification if new functionality is needed)
Instead, create
ClassA
subclasses parallel to theClassB
subclassesHandle
ClassB
subclass creation logic inside parallel classes respectively
One Last Note
I am not saying if-else is bad always! When the conditions are so simple then you have to use if-else, for example:
int x = 10;
if(x % 2 == 0) System.out.println("Even");
else System.out.println("Odd");
another example:
if (user.isLoggedIn()) {
showDashboard();
} else {
showLogin();
}
So, when the situation is to manage code extensibility and handle complex logic, always think about the Factory Method Design Pattern, whether it fits your requirements or not!