Factory Method Pattern

Factory Method Pattern

THE WHAT

The Factory Method is a way to create objects without specifying their exact class (or using the new operator in the client). Instead, you have a base class with a method for creating objects, and subclasses of this base class decide what specific type of object to create. This makes it easy to choose or switch the type of object being created without changing the main code that uses them.

In other words…

The Factory Method is like a tool that helps you make different types of products. You have a main tool (base class) that knows how to make a product, but the details of what specific product to make are decided by different versions of the tool (subclasses). This way, you can add new types of products without changing the main tool.

The General Problem it tries to solve

The Factory Method addresses the scenarios where the exact type of object isn’t known until runtime, or when the creation process is complex and should be centralized to manage changes more effectively.

Example of the General Problem

Consider a software system that manages various types of documents. Initially, it supports text documents and spreadsheets. As the system evolves, new document types, such as presentations, are added. Hardcoding the creation logic for each document type within the client code leads to tight coupling and makes the system difficult to maintain and extend. The Factory Method provides a way to decouple the client code from the specific classes it instantiates, allowing new document types to be added with minimal changes to the existing code.

THE WHEN

You should consider using the Factory Method when:

  • You don’t know beforehand the exact types and dependencies of the objects your code should work with.
  • A Class Wants Its Subclasses to Specify the Objects It Creates
  • Classes Delegate Responsibility to One of Several Helper Subclasses, and You Want to Localize the Knowledge of Which Helper Subclass is the Delegate

THE HOW

  1. Define a Product Interface: Ensure all products follow the same interface, which declares methods that make sense for every product.

public interface Document {
    void open();
    void close();
    void save();
}        

2. Create Concrete Products: Implement different versions of the product interface.

public class TextDocument implements Document {
    @Override
    public void open() { /* ... */ }
    @Override
    public void close() { /* ... */ }
    @Override
    public void save() { /* ... */ }
}

public class SpreadsheetDocument implements Document {
    @Override
    public void open() { /* ... */ }
    @Override
    public void close() { /* ... */ }
    @Override
    public void save() { /* ... */ }
}

public class PresentationDocument implements Document {
    @Override
    public void open() { /* ... */ }
    @Override
    public void close() { /* ... */ }
    @Override
    public void save() { /* ... */ }
}        

3. Define a Creator Class: Declare a factory method that returns new product objects. The return type of this method should match the product interface.

public abstract class DocumentViewer {
    // Factory method
    public abstract Document createDocument();

    public void openDocument() {
        Document doc = createDocument();
        doc.open();
        // additional operations
    }

    public void closeDocument() {
        Document doc = createDocument();
        doc.close();
        // additional operations
    }

    public void saveDocument() {
        Document doc = createDocument();
        doc.save();
        // additional operations
    }
}        

4. Create Concrete Creators: Subclass the creator class and override the factory method to return different types of products.

public class TextDocumentViewer extends DocumentViewer {
    @Override
    public Document createDocument() {
        return new TextDocument();
    }
}

public class SpreadDocumentViewer extends DocumentViewer {
    @Override
    public Document createDocument() {
        return new SpreadsheetDocument();
    }
}

public class PresentationDocumentViewer extends DocumentViewer {
    @Override
    public Document createDocument() {
        return new PresentationDocument();
    }
}        

5. Create a Factory Class: Create a factory class to bind it all together.

public class DocumentViewerFactory {
    public static DocumentViewer getDocumentViewer(String type) {
        switch (type.toLowerCase()) {
            case "text":
                return new TextDocumentViewer();
            case "spreadsheet":
                return new SpreadsheetDocumentViewer();
            case "presentation":
                return new PresentationDocumentViewer();
            default:
                throw new IllegalArgumentException("Unknown document type: " + type);
        }
    }
}        
public class Main {
    public static void main(String[] args) {
        // Example usage of the factory
        DocumentViewer textViewer = DocumentViewerFactory.getDocumentViewer("text");
        textViewer.openDocument();
        textViewer.saveDocument();
        textViewer.closeDocument();

        DocumentViewer spreadsheetViewer = DocumentViewerFactory.getDocumentViewer("spreadsheet");
        spreadsheetViewer.openDocument();
        spreadsheetViewer.saveDocument();
        spreadsheetViewer.closeDocument();

        DocumentViewer presentationViewer = DocumentViewerFactory.getDocumentViewer("presentation");
        presentationViewer.openDocument();
        presentationViewer.saveDocument();
        presentationViewer.closeDocument();
    }
}        

UML Diagram

Article content


GENERIC UML DIAGRAM

Article content

THE CONSEQUENCES

Pros

  1. Avoids Tight Coupling: The creator is decoupled from the concrete products it creates.
  2. Single Responsibility Principle: Product creation code is centralized, making it easier to manage and maintain.
  3. Open/Closed Principle: New product types can be added without modifying existing client code.

Cons

  1. Increased Complexity: The pattern introduces additional classes and subclasses, which can complicate the codebase.
  2. Proliferation of Subclasses: For each new product type, a new subclass of the creator is typically needed.

The Tradeoffs

While the Factory Method pattern provides flexibility and decouples the client code from specific classes, it also introduces additional complexity. This complexity is a tradeoff for the maintainability and extensibility gains. Therefore, it's crucial to evaluate whether the benefits outweigh the costs in your particular context.

Conclusion

The Factory Method pattern is a powerful tool for creating objects in a flexible and maintainable way. By encapsulating the object creation process, it adheres to key design principles, making it easier to extend and manage code. However, it's essential to balance its benefits against the complexity it introduces, ensuring that its use adds value to your project.

Thank you

To view or add a comment, sign in

More articles by Chirag Vaswani

  • Iterator Pattern

    WHAT IS ITERATOR PATTERN? Iterator pattern allows us to traverse elements of a collection without exposing its…

  • Command Pattern

    The Command design pattern encapsulates a request as an object, thereby allowing users to parameterize clients with…

  • Chain of Responsibility Pattern

    Ever faced challenges managing complex workflows with multiple handlers in a serial manner? CoR to the rescue… THE WHAT…

  • Proxy Pattern

    Ever wondered how you can manage heavy resources in your applications without slowing down performance? There is way to…

  • Flyweight Pattern

    Flyweight is a class in boxing which includes fighters weighing up to and including 51 kg (112 lb) for a title fight…

  • Facade Pattern

    The What The Facade Pattern is a structural design pattern that provides a simplified interface to a complex subsystem,…

  • Decorator Pattern

    The Decorator Pattern is a structural design pattern that allows you to dynamically attach additional responsibilities…

  • Composite Pattern

    Have you ever struggled with managing complex hierarchical structures in your code? Composite pattern can simplify your…

  • Bridge Pattern

    Ever felt overwhelmed by the explosion of subclasses when trying to support multiple variations in your code? What if…

  • Adaptor Pattern

    The Adapter Pattern is about creating an intermediary abstraction that translates, or adapts, one interface into…

Others also viewed

Explore content categories