LLDCreational Design PatternsFactory Method

Factory Method

Creational Design Patterns

Imagine you're designing a logistics management application. Your core module needs to create Transport objects to deliver goods. Initially, the company only uses trucks. So, you might write your code like this:

public class LogisticsService {
    public void planDelivery(Cargo cargo) {
        // ... some logic to plan the route

        // The problem is here: direct coupling to a concrete class
        Truck truck = new Truck();
        truck.load(cargo);
        truck.dispatch();
    }
}

This code is tightly coupled. The LogisticsService knows explicitly about the Truck class. What happens when the business expands to use ships for overseas delivery? You would have to go back into the LogisticsService, add an if-else statement (if (type == 'land') new Truck() else if (type == 'sea') new Ship()), and modify its core logic.

This violates the Open/Closed Principle. The high-level LogisticsService is not closed for modification when a new transport type is introduced. How can we decouple the LogisticsService from the specific type of transport it needs to create?

The Pattern: Let Subclasses Decide

Factory Method says: the parent class defines when an object is needed, but each child class decides what object to build.
Instead of calling new Truck() (or new Ship()) in the parent, the parent calls an overridable method—​its factory method.
Every child overrides this method and returns the product it knows about.

Because creation happens in the child, you can add new products by adding new subclasses, without touching existing code. That’s why the pattern is sometimes called a “virtual constructor.”

The Participants

The Factory Method pattern has four key components:

  1. Product: The interface or abstract class for the objects the factory method creates (e.g., ITransport).
  2. Concrete Product: The actual classes that implement the Product interface (e.g., Truck, Ship).
  3. Creator: An abstract class that declares the factory method, which returns an object of the Product type. It contains the core business logic that relies on the Product, but it doesn't know which concrete product to create.
  4. Concrete Creator: Subclasses that override the factory method to return an instance of a specific Concrete Product.

Refactoring The Logistics App

Let's refactor our LogisticsService to use the Factory Method pattern.

Step 1: Define the Product Interface This defines the contract for any type of transport.

public interface ITransport {
    void load(Cargo cargo);
    void dispatch();
}

// Concrete Products
public class Truck implements ITransport { /* ... implementation ... */ }
public class Ship implements ITransport { /* ... implementation ... */ }

Step 2: Create the Abstract Creator with the Factory Method The Logistics creator class contains the high-level business logic. It knows it needs some kind of transport, but delegates the actual creation to its subclasses via the abstract createTransport() factory method.

public abstract class Logistics {

    // This is the Factory Method. It's abstract, forcing subclasses to implement it.
    public abstract ITransport createTransport();

    // This is the core business logic. It is not coupled to any concrete transport type.
    // It works with any object that satisfies the ITransport interface.
    public void planDelivery(Cargo cargo) {
        // ... some logic to plan the route

        ITransport transport = createTransport(); // Call the factory method to get the object
        transport.load(cargo);
        transport.dispatch();
    }
}

Step 3: Create Concrete Creators Each concrete creator knows which specific product to instantiate.

public class RoadLogistics extends Logistics {
    @Override
    public ITransport createTransport() {
        // This creator is responsible for creating Trucks.
        return new Truck();
    }
}

public class SeaLogistics extends Logistics {
    @Override
    public ITransport createTransport() {
        // This creator is responsible for creating Ships.
        return new Ship();
    }
}

Step 4: Using the Factory in the Client Code

Now, the client (e.g., the main application class) can decide which kind of logistics it needs at runtime. The client is coupled to the concrete creator, but the creator's core logic remains decoupled from the concrete product.

public class Application {
    private static Logistics logistics;

    public static void main(String[] args) {
        // Read a configuration file, get user input, etc.
        String transportType = "sea"; // Or "road"

        if (transportType.equalsIgnoreCase("road")) {
            logistics = new RoadLogistics();
        } else if (transportType.equalsIgnoreCase("sea")) {
            logistics = new SeaLogistics();
        } else {
            throw new IllegalArgumentException("Unknown transport type");
        }

        // The client code works with the abstract creator.
        logistics.planDelivery(new Cargo());
    }
}

Now, the client code can decide which kind of logistics it needs, and the correct transport will be created automatically, without the Logistics base class ever knowing the details.

Factory Method vs. Simple Factory

A common source of confusion is the difference between the Factory Method pattern and a "Simple Factory" or "Static Factory". A Simple Factory is a single class with one method that has a large if-else or switch statement to create objects.

// This is NOT the Factory Method pattern. It's a "Simple Factory".
public class TransportFactory {
    public static ITransport createTransport(String type) {
        if ("truck".equalsIgnoreCase(type)) {
            return new Truck();
        } else if ("ship".equalsIgnoreCase(type)) {
            return new Ship();
        }
        // If we add a Plane, we have to modify this method!
        return null;
    }
}

Key Differences:

  • Extensibility: The Factory Method pattern uses inheritance and forces subclasses to provide the implementation, adhering to the Open/Closed Principle. To add a Plane, you create a new AirLogistics class and you don't touch any existing code. In a Simple Factory, you must modify the central factory class, which violates the OCP.
  • Implementation: Factory Method relies on an abstract method that subclasses implement. Simple Factory is just a concrete class with a static method that returns different types.

While a Simple Factory can be useful for centralizing object creation, the Factory Method is a true Gang of Four pattern that provides much greater flexibility and extensibility in complex systems. Knowing this distinction is a sign of a mature understanding of design patterns.

Interview Focus: Analysis and Trade-offs

Benefits:

  • Excellent for SOLID Principles: This is a key point.
    • SRP: It moves the responsibility of object creation into a dedicated place (the factory method and its subclasses), separating it from the core business logic of the creator class.
    • OCP: It makes the system highly extensible. You can introduce a new AirLogistics creator and Plane product without touching any of the existing Logistics, RoadLogistics, or SeaLogistics classes.
  • Decoupling: The creator class is completely decoupled from the concrete products, depending only on their common interface.

Drawbacks:

  • Increased Complexity: The primary disadvantage is that you end up creating a parallel hierarchy of creator classes alongside your product classes. For a simple case, this can feel like overkill.

How Factory Method Relates to Other Concepts

  • Relationship to Template Method: The Factory Method pattern is often a specialized version of the Template Method pattern. In our example, planDelivery() acts as a Template Method: it defines the skeleton of the delivery algorithm. One of the steps in that algorithm, createTransport(), is the factory method that subclasses must implement.
  • Enabling Loose Coupling: This pattern is a fundamental technique for building loosely coupled systems. It allows high-level components to work with abstractions while delegating the creation of specific implementation details to another part of the system.
  • Implementing Dependency Inversion: The Factory Method is a way to implement the Dependency Inversion Principle. Our high-level Logistics class does not depend on the low-level Truck or Ship classes. Instead, both depend on the ITransport abstraction, effectively "inverting" the dependency.
  • Foundation for More Complex Factories: Understanding the Factory Method is essential before moving on to the more complex Abstract Factory pattern, which uses factory methods to create families of related objects.