Design Patterns - Factory Method (in Java)

Design Patterns - Factory Method (in Java)

Don't do partial inheritance

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

  1. We want to start our logistics business (Logistic class)

    • Initially, we consider two services: road logistic and sea logistic
  2. 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 Pattern

  • planDelivery() doesn’t depend on type 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,

  1. Suppose ClassB has multiple subclasses, ClassB1, ClassB2, … …, ClassBn

  2. If ClassA HAS-A ClassB object, don’t handle ClassB subclass creation logic inside ClassA

    • this will force you to put if-else condition

    • if-else is not extensible (requires further modification if new functionality is needed)

  3. Instead, create ClassA subclasses parallel to the ClassB subclasses

  4. Handle 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!