Introduction
The Abstract Factory pattern is a creational design pattern that provides an interface for creating families of related or dependent objects without specifying their concrete classes. This pattern is particularly useful when a system needs to be independent of how its objects are created, composed, and represented.
Key Concepts
- Abstract Factory: Declares an interface for creating abstract product objects.
- Concrete Factory: Implements the operations to create concrete product objects.
- Abstract Product: Declares an interface for a type of product object.
- Concrete Product: Defines a product object to be created by the corresponding concrete factory and implements the Abstract Product interface.
- Client: Uses only interfaces declared by Abstract Factory and Abstract Product classes.
Structure
The structure of the Abstract Factory pattern can be visualized as follows:
AbstractFactory |-- ConcreteFactory1 | |-- createProductA() | |-- createProductB() | |-- ConcreteFactory2 |-- createProductA() |-- createProductB() AbstractProductA |-- ConcreteProductA1 |-- ConcreteProductA2 AbstractProductB |-- ConcreteProductB1 |-- ConcreteProductB2 Client |-- uses AbstractFactory and AbstractProduct interfaces
Example
Let's consider an example where we need to create a family of products: Chair
and Sofa
. We will have two variants of these products: Modern
and Victorian
.
Step-by-Step Implementation
- Define Abstract Products:
from abc import ABC, abstractmethod class Chair(ABC): @abstractmethod def sit_on(self) -> str: pass class Sofa(ABC): @abstractmethod def lie_on(self) -> str: pass
- Define Concrete Products:
class ModernChair(Chair): def sit_on(self) -> str: return "Sitting on a modern chair." class VictorianChair(Chair): def sit_on(self) -> str: return "Sitting on a Victorian chair." class ModernSofa(Sofa): def lie_on(self) -> str: return "Lying on a modern sofa." class VictorianSofa(Sofa): def lie_on(self) -> str: return "Lying on a Victorian sofa."
- Define Abstract Factory:
class FurnitureFactory(ABC): @abstractmethod def create_chair(self) -> Chair: pass @abstractmethod def create_sofa(self) -> Sofa: pass
- Define Concrete Factories:
class ModernFurnitureFactory(FurnitureFactory): def create_chair(self) -> Chair: return ModernChair() def create_sofa(self) -> Sofa: return ModernSofa() class VictorianFurnitureFactory(FurnitureFactory): def create_chair(self) -> Chair: return VictorianChair() def create_sofa(self) -> Sofa: return VictorianSofa()
- Client Code:
def client_code(factory: FurnitureFactory) -> None: chair = factory.create_chair() sofa = factory.create_sofa() print(chair.sit_on()) print(sofa.lie_on()) if __name__ == "__main__": print("Client: Testing client code with the ModernFurnitureFactory:") client_code(ModernFurnitureFactory()) print("\nClient: Testing the same client code with the VictorianFurnitureFactory:") client_code(VictorianFurnitureFactory())
Explanation
- Abstract Products (
Chair
,Sofa
): Define interfaces for different types of products. - Concrete Products (
ModernChair
,VictorianChair
,ModernSofa
,VictorianSofa
): Implement the interfaces defined by Abstract Products. - Abstract Factory (
FurnitureFactory
): Declares methods for creating abstract products. - Concrete Factories (
ModernFurnitureFactory
,VictorianFurnitureFactory
): Implement the methods declared by the Abstract Factory to create concrete products. - Client Code: Uses the Abstract Factory and Abstract Products interfaces to create and use products without knowing their concrete classes.
Practical Exercise
Exercise
Create an Abstract Factory pattern for a family of products: Button
and Checkbox
. Implement two variants: Windows
and Mac
.
- Define Abstract Products:
Button
andCheckbox
. - Define Concrete Products:
WindowsButton
,MacButton
,WindowsCheckbox
,MacCheckbox
. - Define Abstract Factory:
GUIFactory
. - Define Concrete Factories:
WindowsFactory
,MacFactory
. - Implement client code to use these factories.
Solution
- Define Abstract Products:
class Button(ABC): @abstractmethod def click(self) -> str: pass class Checkbox(ABC): @abstractmethod def check(self) -> str: pass
- Define Concrete Products:
class WindowsButton(Button): def click(self) -> str: return "Windows button clicked." class MacButton(Button): def click(self) -> str: return "Mac button clicked." class WindowsCheckbox(Checkbox): def check(self) -> str: return "Windows checkbox checked." class MacCheckbox(Checkbox): def check(self) -> str: return "Mac checkbox checked."
- Define Abstract Factory:
class GUIFactory(ABC): @abstractmethod def create_button(self) -> Button: pass @abstractmethod def create_checkbox(self) -> Checkbox: pass
- Define Concrete Factories:
class WindowsFactory(GUIFactory): def create_button(self) -> Button: return WindowsButton() def create_checkbox(self) -> Checkbox: return WindowsCheckbox() class MacFactory(GUIFactory): def create_button(self) -> Button: return MacButton() def create_checkbox(self) -> Checkbox: return MacCheckbox()
- Client Code:
def client_code(factory: GUIFactory) -> None: button = factory.create_button() checkbox = factory.create_checkbox() print(button.click()) print(checkbox.check()) if __name__ == "__main__": print("Client: Testing client code with the WindowsFactory:") client_code(WindowsFactory()) print("\nClient: Testing the same client code with the MacFactory:") client_code(MacFactory())
Common Mistakes and Tips
- Mistake: Forgetting to implement all methods in concrete products.
- Tip: Ensure that all methods declared in abstract products are implemented in concrete products.
- Mistake: Using concrete classes directly in client code.
- Tip: Always use abstract interfaces in client code to maintain flexibility and scalability.
Conclusion
The Abstract Factory pattern is a powerful tool for creating families of related objects without specifying their concrete classes. It promotes consistency among products and enhances the scalability of the system. By following the structured approach and practical example provided, you should now have a solid understanding of how to implement and use the Abstract Factory pattern in your projects.
Software Design Patterns Course
Module 1: Introduction to Design Patterns
- What are Design Patterns?
- History and Origin of Design Patterns
- Classification of Design Patterns
- Advantages and Disadvantages of Using Design Patterns
Module 2: Creational Patterns
Module 3: Structural Patterns
Module 4: Behavioral Patterns
- Introduction to Behavioral Patterns
- Chain of Responsibility
- Command
- Interpreter
- Iterator
- Mediator
- Memento
- Observer
- State
- Strategy
- Template Method
- Visitor
Module 5: Application of Design Patterns
- How to Select the Right Pattern
- Practical Examples of Pattern Usage
- Design Patterns in Real Projects
- Refactoring Using Design Patterns
Module 6: Advanced Design Patterns
- Design Patterns in Modern Architectures
- Design Patterns in Microservices
- Design Patterns in Distributed Systems
- Design Patterns in Agile Development