1.2 Factory Method - Design Pattern: A Practical Guide

A fundamental component of creational design patterns in the field of software design patterns is the Factory Method pattern. Without mentioning the precise class of the object that will be formed, it offers a sophisticated solution to the problem of constructing objects. The Factory Method paradigm facilitates code extensibility, encapsulation, and flexibility by assigning the instantiation process to subclasses. We will discuss the fundamentals of the Factory Method pattern, look at how it is implemented in Java, and use an actual example to show how to use it.

Understanding the Factory Method Pattern:

The core idea of the Factory Method pattern is to define an interface for object creation, but to give subclasses the ability to modify the kind of objects that are generated. It provides a mechanism for subclasses to instantiate objects while abiding by a common interface or superclass by encapsulating the logic involved in object generation. Usually, this pattern includes the following essential elements:

  1. Creator: An object of a type provided by a subclass is returned by the factory method, which is declared by this abstract class or interface.

  2. Concrete Creator: To produce particular kinds of objects, subclasses of the creator class implement the factory function.

  3. Product: The objects made using the factory method's interface, or superclass, is this.

  4. Concrete Product: These are the concrete implementations of the product interface or superclass.

Implementing the Factory Method Pattern in Java:

Let's dive into a simple implementation of the Factory Method pattern in Java:

// Product interface
interface Product {
    void operation();
}
// Concrete products
class ConcreteProductA implements Product {
    @Override
    public void operation() {
        System.out.println("Operation performed by ConcreteProductA.");
    }
}
class ConcreteProductB implements Product {
    @Override
    public void operation() {
        System.out.println("Operation performed by ConcreteProductB.");
    }
}
// Creator interface
interface Creator {
    Product createProduct();
}
// Concrete creators
class ConcreteCreatorA implements Creator {
    @Override
    public Product createProduct() {
        return new ConcreteProductA();
    }
}
class ConcreteCreatorB implements Creator {
    @Override
    public Product createProduct() {
        return new ConcreteProductB();
    }
}

Understanding the Implementation:

  • The Product interface defines the operations that concrete products must implement.

  • Concrete products (ConcreteProductA and ConcreteProductB) provide specific implementations of the Product interface.

  • The Creator interface declares the factory method createProduct(), which returns a Product object.

  • Concrete creators (ConcreteCreatorA and ConcreteCreatorB) implement the createProduct() method to create specific types of products.

Example Usage of Factory Method Pattern:

Let's consider a scenario where we have a document editor application with multiple document types:

class DocumentEditor {
    private Creator creator;
    public DocumentEditor(Creator creator) {
        this.creator = creator;
    }
    public void createDocument() {
        Product product = creator.createProduct();
        product.operation();
    }
}

Now, we can use the DocumentEditor class to create documents using different creators:

class Main {
    public static void main(String[] args) {
        Creator creatorA = new ConcreteCreatorA();
        DocumentEditor editorA = new DocumentEditor(creatorA);
        editorA.createDocument(); // Creates a document using ConcreteCreatorA
        Creator creatorB = new ConcreteCreatorB();
        DocumentEditor editorB = new DocumentEditor(creatorB);
        editorB.createDocument(); // Creates a document using ConcreteCreatorB
    }
}

In this example, the DocumentEditor class is decoupled from the specific implementations of document creation, allowing us to easily switch between different document types by changing the creator instance.

Conclusion:

Without strongly tying the client code to the concrete implementations, Factory Method design offers an adaptable and extensible mechanism to build objects. The Factory Method pattern facilitates code reuse, maintainability, and scalability by providing an interface for object creation and assigning the instantiation task to subclasses. Learning the Factory Method pattern will greatly improve your software design abilities and enable you to create more reliable and flexible applications, regardless of whether you're creating document editors, GUI frameworks, or gaming engines.