Drani Academy – Interview Question, Search Job, Tuitorials, Cheat Sheet, Project, eBook

Design Patterns

Tutorials – Design Patterns

 
Chapter 4: Factory Method Pattern

 

The Factory Method Pattern is a creational design pattern that provides an interface for creating objects in a super class, but allows subclasses to alter the type of objects that will be created. In this chapter, we will explore the Factory Method Pattern, its use cases, implementation, and benefits.

Understanding the Factory Method Pattern

The Factory Method Pattern is all about creating objects. It defines an interface for creating objects but lets subclasses alter the type of objects that will be created. This is achieved by creating a separate factory method for each type of object that needs to be created.

Key Characteristics of Factory Method Pattern:

  • Abstract Creator: The pattern defines an abstract creator (interface or base class) that declares the factory method, which returns an object of a product type.
  • Concrete Creators: Subclasses of the abstract creator implement the factory method, each providing their own implementation to create a specific type of product.
  • Abstract Product: The pattern also defines an abstract product (interface or base class) that represents the type of objects to be created.
  • Concrete Products: Subclasses of the abstract product are the actual product implementations.
  • Loose Coupling: The Factory Method Pattern promotes loose coupling between the creator and the product. The creator class doesn’t need to know the concrete product classes, only the abstract product.

Use Cases for the Factory Method Pattern

The Factory Method Pattern is used in various scenarios where object creation needs to be flexible and customizable. Some common use cases include:

1. GUI Libraries:

In graphical user interface (GUI) libraries, the Factory Method Pattern is often used to create platform-specific UI elements. For example, a button creator factory can create Windows buttons on a Windows platform and Mac buttons on a Mac platform.

interface Button {
    void render();
}
class WindowsButton implements Button {
    @Override
    public void render() {
        // Render a Windows button
    }
}
class MacButton implements Button {
    @Override
    public void render() {
        // Render a Mac button
    }
}
interface ButtonFactory {
    Button createButton();
}
class WindowsButtonFactory implements ButtonFactory {
    @Override
    public Button createButton() {
        return new WindowsButton();
    }
}
class MacButtonFactory implements ButtonFactory {
    @Override
    public Button createButton() {
        return new MacButton();
    }
}

2. Logging Frameworks:

In logging frameworks, the Factory Method Pattern is used to create loggers for different log levels and destinations. Each logger type can be a concrete product.

interface Logger {
    void log(String message);
}
class ConsoleLogger implements Logger {
    @Override
    public void log(String message) {
        System.out.println("Console: " + message);
    }
}
class FileLogger implements Logger {
    @Override
    public void log(String message) {
        // Log to a file
    }
}
interface LoggerFactory {
    Logger createLogger();
}
class ConsoleLoggerFactory implements LoggerFactory {
    @Override
    public Logger createLogger() {
        return new ConsoleLogger();
    }
}
class FileLoggerFactory implements LoggerFactory {
    @Override
    public Logger createLogger() {
        return new FileLogger();
    }
}

3. Game Development:

In game development, the Factory Method Pattern can be used to create different types of game entities like characters, enemies, and power-ups. Each entity type can have its factory.

abstract class GameEntity {
    abstract void render();
}
class Character extends GameEntity {
    @Override
    void render() {
        // Render the character
    }
}
class Enemy extends GameEntity {
    @Override
    void render() {
        // Render the enemy
    }
}
abstract class GameEntityFactory {
    abstract GameEntity createEntity();
}
class CharacterFactory extends GameEntityFactory {
    @Override
    GameEntity createEntity() {
        return new Character();
    }
}
class EnemyFactory extends GameEntityFactory {
    @Override
    GameEntity createEntity() {
        return new Enemy();
    }
}

Implementing the Factory Method Pattern

The Factory Method Pattern can be implemented in various programming languages, including Java, C++, and Python. The key is to define an abstract creator class or interface and concrete creator classes that implement the factory method to create specific product objects.

Java Implementation:

In Java, the Factory Method Pattern can be implemented using abstract classes and method overriding.

abstract class Product {
    abstract void performAction();
}
class ConcreteProductA extends Product {
    @Override
    void performAction() {
        // Implement action for ConcreteProductA
    }
}
class ConcreteProductB extends Product {
    @Override
    void performAction() {
        // Implement action for ConcreteProductB
    }
}
abstract class Creator {
    abstract Product factoryMethod();
    void someOperation() {
        // ...
        Product product = factoryMethod();
        product.performAction();
        // ...
    }
}
class ConcreteCreatorA extends Creator {
    @Override
    Product factoryMethod() {
        return new ConcreteProductA();
    }
}
class ConcreteCreatorB extends Creator {
    @Override
    Product factoryMethod() {
        return new ConcreteProductB();
    }
}

Python Implementation:

In Python, the Factory Method Pattern can be implemented using abstract base classes and inheritance.

from abc import ABC, abstractmethod
class Product(ABC):
    @abstractmethod
    def perform_action(self):
        pass
class ConcreteProductA(Product):
    def perform_action(self):
        # Implement action for ConcreteProductA
        pass
class ConcreteProductB(Product):
    def perform_action(self):
        # Implement action for ConcreteProductB
        pass
class Creator(ABC):
    @abstractmethod
    def factory_method(self):
        pass
    def some_operation(self):
        # ...
        product = self.factory_method()
        product.perform_action()
        # ...
class ConcreteCreatorA(Creator):
    def factory_method(self):
        return ConcreteProductA()
class ConcreteCreatorB(Creator):
    def factory_method(self):
        return ConcreteProductB()

Benefits of the Factory Method Pattern

The Factory Method Pattern offers several benefits:

  • Flexibility: It allows for the creation of objects to be customized by subclasses, providing flexibility in object creation.
  • Extensibility: New product types can be added without modifying existing code, promoting an open-closed principle.
  • Code Reusability: The pattern promotes code reusability by separating the product creation from its usage.
  • Encapsulation: It encapsulates the object creation details, making the code more maintainable and organized.

Drawbacks and Considerations

While the Factory Method Pattern provides flexibility and extensibility, it also has some considerations:

  • Complexity: When there are many product types and corresponding creator classes, the pattern can lead to a complex class hierarchy.
  • Abstraction Overhead: It introduces additional abstraction layers, which may not be necessary in simple scenarios.
  • Choosing the Right Creator: It’s crucial to choose the right creator for a given situation. The choice of creator can affect the type of product created.
  • Naming Conventions: Naming conventions for creators and products should be consistent and clear to maintain code readability.

Conclusion

The Factory Method Pattern is a powerful tool for creating objects in a flexible and extensible way. By defining an abstract creator class or interface, the pattern allows subclasses to determine the type of objects to create. This separation of concerns enhances code organization and makes it easier to add new product types. When used wisely, the Factory Method Pattern can contribute to cleaner and more maintainable code, providing a balance between flexibility and structure in software design.

Scroll to Top