Factory Method Design Pattern – Design Better

Published by

on

The Factory Method design pattern is one of the most well-known and frequently used design patterns in software development. It falls under the category of creational patterns, which focus on ways to instantiate objects. The primary goal of the Factory Method is to provide a flexible and scalable way to create objects without directly specifying the exact class of the object that will be created. Instead, object creation is deferred to subclasses or specialized factory classes.

What Is the Factory Method Pattern?

The Factory Method pattern defines an interface for creating an object but allows subclasses or other classes to decide which class to instantiate. Instead of calling the constructor of the class directly (e.g., new ClassName()), you call a method (the “factory method”) that returns an instance of the desired class. This helps in scenarios where the type of object is not known at runtime or if the object creation process is complex and must be abstracted away.

Real-Life Analogy

Imagine you walk into a restaurant that serves different kinds of pizza. You, as the customer, don’t need to know the exact steps for making the pizza; you just need to place your order. The restaurant will determine how to make the pizza based on your request and the available ingredients. In this analogy:

  • The customer is the client that asks for an object.
  • The restaurant is the factory that produces the object.
  • The type of pizza (e.g., Margherita, Pepperoni) is the specific subclass of the object being created.
  • The factory method is the process that determines which pizza to make and returns it to the customer.

Thus, the factory hides the complexity and provides a simplified way to get what you need.


Why Use the Factory Method?

1. Flexibility and Decoupling

The primary advantage of the Factory Method pattern is its flexibility. The code that requests an object (client code) doesn’t need to know the details about how to create that object. This leads to a system that is easier to maintain and extend. If a new type of object needs to be added, you don’t need to change the client code; you only need to update the factory.

2. Code Reusability

By abstracting object creation, the Factory Method ensures that code responsible for creating objects can be reused across the application. It reduces code duplication since the object creation logic is centralized in the factory.

3. Complex Object Creation

In some cases, creating an object may involve several steps, such as configuring settings, initializing properties, or performing validations. The Factory Method encapsulates this complexity, making the client code cleaner and easier to read.

4. Promotes Open/Closed Principle

The Factory Method pattern supports the Open/Closed Principle of object-oriented design, which means that the system can be extended with new functionality (new types of objects) without modifying existing code.


A General Structure of Factory Method

Here’s a high-level look at how the Factory Method pattern is structured:

  1. Creator: This is an abstract class or interface that declares the factory method.
  2. Concrete Creators: These are subclasses that implement the factory method to create instances of specific types.
  3. Product: This is the abstract or interface class for the type of object that will be created.
  4. Concrete Products: These are the concrete implementations of the Product.

Example of Factory Method Pattern in Real Life

Consider an online store that sells electronics such as laptops and smartphones. Each product (laptop, smartphone) might have different ways of being prepared and packaged before shipment. The store doesn’t need to know how to prepare each specific product—it just asks the factory to create the appropriate product.

  • The Store is the client.
  • The Factory is responsible for creating the specific product (laptop or smartphone).
  • Products (Laptop, Smartphone) are the concrete implementations of the product being created.

In this case, the factory method would be responsible for deciding which product to instantiate (Laptop or Smartphone) based on input from the store (e.g., a customer order).


Factory Method Pattern in Programming Languages

Let’s look at how the Factory Method pattern can be implemented in Java, C#, and Node.js.

1. Factory Method in Java

Example 1: Core Java Implementation

In this Java example, let’s assume we are creating different types of vehicles (Car and Bike) via a factory.

// Step 1: Product Interface
interface Vehicle {
    void start();
}

// Step 2: Concrete Product Classes
class Car implements Vehicle {
    public void start() {
        System.out.println("Car is starting...");
    }
}

class Bike implements Vehicle {
    public void start() {
        System.out.println("Bike is starting...");
    }
}

// Step 3: Creator (Factory)
abstract class VehicleFactory {
    public abstract Vehicle createVehicle();
}

// Step 4: Concrete Creators
class CarFactory extends VehicleFactory {
    public Vehicle createVehicle() {
        return new Car();
    }
}

class BikeFactory extends VehicleFactory {
    public Vehicle createVehicle() {
        return new Bike();
    }
}

// Step 5: Client Code
public class FactoryMethodExample {
    public static void main(String[] args) {
        VehicleFactory factory = new CarFactory(); // can dynamically change this to BikeFactory
        Vehicle myVehicle = factory.createVehicle();
        myVehicle.start();  // Output: Car is starting...
    }
}

In this example:

  • The Vehicle interface defines the type of objects that the factory will create.
  • The Car and Bike classes implement this interface.
  • The VehicleFactory class declares a createVehicle method.
  • Concrete factories like CarFactory and BikeFactory implement the createVehicle method to return a specific type of vehicle.
Example 2: Spring Boot Example

In Spring Boot, you could use the Factory Method pattern to control how beans are instantiated.

@Service
public class NotificationFactory {

    public NotificationService createNotification(String channel) {
        switch (channel) {
            case "SMS":
                return new SmsNotification();
            case "EMAIL":
                return new EmailNotification();
            default:
                throw new IllegalArgumentException("Unknown channel " + channel);
        }
    }
}

public interface NotificationService {
    void send(String message);
}

public class SmsNotification implements NotificationService {
    public void send(String message) {
        System.out.println("Sending SMS: " + message);
    }
}

public class EmailNotification implements NotificationService {
    public void send(String message) {
        System.out.println("Sending Email: " + message);
    }
}

// In a Controller
@RestController
public class NotificationController {

    private final NotificationFactory notificationFactory;

    public NotificationController(NotificationFactory notificationFactory) {
        this.notificationFactory = notificationFactory;
    }

    @PostMapping("/send")
    public void sendNotification(@RequestParam String message, @RequestParam String channel) {
        NotificationService notification = notificationFactory.createNotification(channel);
        notification.send(message);
    }
}

Here, the NotificationFactory creates a specific type of notification service based on the channel (SMS or Email).


2. Factory Method in C#

Here’s a basic example of how to implement the Factory Method pattern in C#.

// Step 1: Product Interface
public interface ITransport {
    void Deliver();
}

// Step 2: Concrete Product Classes
public class Truck : ITransport {
    public void Deliver() {
        Console.WriteLine("Delivery by Truck");
    }
}

public class Ship : ITransport {
    public void Deliver() {
        Console.WriteLine("Delivery by Ship");
    }
}

// Step 3: Creator (Factory)
public abstract class TransportFactory {
    public abstract ITransport CreateTransport();
}

// Step 4: Concrete Factories
public class TruckFactory : TransportFactory {
    public override ITransport CreateTransport() {
        return new Truck();
    }
}

public class ShipFactory : TransportFactory {
    public override ITransport CreateTransport() {
        return new Ship();
    }
}

// Step 5: Client Code
public class Program {
    public static void Main(string[] args) {
        TransportFactory factory = new TruckFactory(); // Or new ShipFactory();
        ITransport transport = factory.CreateTransport();
        transport.Deliver(); // Output: Delivery by Truck
    }
}

Here, Truck and Ship implement the ITransport interface, and the TransportFactory provides a CreateTransport method to instantiate them.


3. Factory Method in Node.js

JavaScript does not have a class-based inheritance system like Java or C#. However, you can still implement the Factory Method pattern using JavaScript’s prototypal inheritance or with classes using Node.js.

// Step 1: Product Classes
class Car {
    start() {
        console.log('Car is starting...');
    }
}

class Bike {
    start() {
        console.log('Bike is starting...');
    }
}

// Step 2: Factory Method
class VehicleFactory {
    static createVehicle(type) {
        switch (type) {
            case 'car':
                return new Car();
            case 'bike':
                return new Bike();
            default:
                throw new Error('Unknown vehicle type');
        }
    }
}

// Step 3: Client Code
const vehicle = VehicleFactory.createVehicle('car');
vehicle.start();  // Output: Car is starting...

Here, the factory method createVehicle is used to instantiate either a Car or Bike object, depending on the input.


Summary

The Factory Method pattern provides a flexible, decoupled way of creating objects, especially when the type of object isn’t known until runtime or if object creation involves complex processes. It promotes scalability, maintainability, and adherence to design principles like Open/Closed and Single Responsibility. By centralizing object creation logic, it also leads to cleaner and more readable code.

When to Use:

  • When the exact type of object that needs to be created isn’t known until runtime.
  • When the creation process is complex or requires centralized control.
  • When you need to encapsulate object creation and hide it from the client.

Across languages like Java, C#, and Node.js, the Factory Method pattern can be implemented to abstract object creation, making applications more flexible, scalable, and easier to maintain.


Disclaimer: This blog contains the individual opinions and perspectives of Vijay Pandurangan, which are not necessarily indicative of the views of his employer. The author assumes no responsibility for any actions taken or decisions made based on the information presented in this blog. Should any content from this blog be referenced or used in articles, white papers, wikis, blogs, or similar formats, it should be attributed solely to Vijay Pandurangan, independent of his professional affiliations. Please note that the use of his employer’s name in such contexts is not permitted.


Discover more from Vijay Pandurangan

Subscribe now to keep reading and get access to the full archive.

Continue reading