Design Patterns in Python

repository·main·Indexed 21 days ago

https://github.com/refactoringguru/design-patterns-python

A collection of Python implementations for all classic Gang of Four (GoF) design patterns. The repository provides both conceptual examples focusing on internal structure and class relationships, and RealWorld examples demonstrating practical applications. It includes detailed implementations of patterns such as Abstract Factory, Adapter (Class and Object), Bridge, Builder, and Chain of Responsibility, requiring Python 3.7 or newer.

Tokens
20.5K
Snippets
54
Records
80
Agent score
75%

What's inside refactoringguru-design-patterns-python

  1. Understand the difference between Conceptual and RealWorld examples

    main

    The repository provides two types of examples for each GoF design pattern:

    1. Conceptual examples: These focus on the internal structure of the pattern. They include detailed comments explaining the roles of each class and how they connect to one another.
    2. RealWorld examples: These demonstrate how the pattern is applied within practical Python applications.

    If you are struggling to understand the roles in a RealWorld example, it is recommended to study the Conceptual example first to grasp the underlying class relationships.

  2. Implement the Facade design pattern

    main

    The Facade pattern provides a simplified interface to a complex set of classes, a library, or a framework. It shields the client from the undesired complexity of the subsystem by delegating requests to the appropriate objects and managing their lifecycle.

    Key Components

    • Facade: Provides simple methods that act as shortcuts to sophisticated subsystem functionality. It can either accept existing subsystem objects during initialization or create them internally.
    • Subsystems: The complex logic and classes that the Facade wraps. Subsystems can accept requests from either the Facade or the client directly.
    • Client: Interacts only with the Facade, often remaining unaware of the underlying subsystem's existence.
    from src.Facade.Conceptual.main import Facade, Subsystem1, Subsystem2
    
    # Option 1: Let the Facade manage the lifecycle of subsystems
    facade = Facade()
    
    # Option 2: Provide existing subsystem objects to the Facade
    sub1 = Subsystem1()
    sub2 = Subsystem2()
    facade = Facade(sub1, sub2)
    
    # Use the simplified interface
    print(facade.operation())
  3. Implement the Iterator pattern in Python

    main

    To implement the Iterator pattern in Python, you should use the abstract classes Iterable and Iterator from the collections.abc module.

    1. The Collection (Iterable): Implement the __iter__() method. This method should return a new instance of a concrete iterator compatible with the collection.
    2. The Iterator (Iterator): Implement the __next__() method. This method must return the next item in the sequence and raise StopIteration when the end of the collection is reached.

    This pattern allows you to traverse elements of a collection without exposing its underlying representation (e.g., whether it is a list, stack, or tree).

    from collections.abc import Iterable, Iterator
    from typing import Any
    
    class MyIterator(Iterator):
        def __init__(self, collection):
            self._collection = collection
            self._position = 0
    
        def __next__(self) -> Any:
            if self._position >= len(self._collection):
                raise StopIteration()
            value = self._collection[self._position]
            self._position += 1
            return value
    
    class MyCollection(Iterable):
        def __init__(self, items: list):
            self._items = items
    
        def __iter__(self) -> MyIterator:
            return MyIterator(self._items)
  4. Implement the Visitor design pattern

    main

    The Visitor pattern lets you separate algorithms from the objects on which they operate. This allows you to add new operations to existing object structures without modifying the structures themselves.

    To implement this pattern, you need four main parts:

    1. Component interface: Declares an accept method that takes a visitor as an argument.
    2. ConcreteComponent classes: Implement the accept method by calling the specific visiting method on the visitor that corresponds to the component's class (e.g., visitor.visit_concrete_component_a(self)).
    3. Visitor interface: Declares a set of visiting methods, one for each concrete component class.
    4. ConcreteVisitor classes: Implement the actual algorithms by defining how to interact with each concrete component.

    Concrete components can have special methods that are not part of the base Component interface. Because the visitor receives the concrete component instance, it can access these specific methods.

    from typing import List
    from abc import ABC, abstractmethod
    
    class Component(ABC):
        @abstractmethod
        def accept(self, visitor: 'Visitor') -> None:
            pass
    
    class ConcreteComponentA(Component):
        def accept(self, visitor: 'Visitor') -> None:
            visitor.visit_concrete_component_a(self)
    
        def exclusive_method_of_concrete_component_a(self) -> str:
            return "A"
    
    class Visitor(ABC):
        @abstractmethod
        def visit_concrete_component_a(self, element: ConcreteComponentA) -> None: 
            pass
    
    class ConcreteVisitor1(Visitor):
        def visit_concrete_component_a(self, element: ConcreteComponentA) -> None:
            print(f"{element.exclusive_method_of_concrete_component_a()} + ConcreteVisitor1")
    
    # Usage
    components = [ConcreteComponentA()]
    visitor = ConcreteVisitor1()
    for component in components:
        component.accept(visitor)
  5. Implement the Factory Method pattern

    main

    The Factory Method pattern provides an interface for creating objects in a superclass, but allows subclasses to alter the type of objects that will be created.

    To implement this pattern, you need four main components:

    1. Product (ABC): An abstract base class defining the interface for all concrete products.
    2. ConcreteProduct: Classes that implement the Product interface.
    3. Creator (ABC): An abstract class that declares the factory_method() which returns a Product. It also typically contains core business logic (some_operation()) that relies on the products.
    4. ConcreteCreator: Subclasses that override factory_method() to return specific instances of ConcreteProduct.

    Client code should interact with creators through the Creator base interface to remain decoupled from specific product implementations.

    from abc import ABC, abstractmethod
    
    class Product(ABC):
        @abstractmethod
        def operation(self) -> str:
            pass
    
    class ConcreteProduct1(Product):
        def operation(self) -> str:
            return "{Result of the ConcreteProduct1}"
    
    class Creator(ABC):
        @abstractmethod
        def factory_method(self) -> Product:
            pass
    
        def some_operation(self) -> str:
            product = self.factory_method()
            return f"Creator: The same creator's code has just worked with {product.operation()}"
    
    class ConcreteCreator1(Creator):
        def factory_method(self) -> Product:
            return ConcreteProduct1()
    
    # Client usage
    def client_code(creator: Creator) -> None:
        print(f"Client: I'm not aware of the creator's class, but it still works.\n{creator.some_operation()}")
    
    if __name__ == "__main__":
        client_code(ConcreteCreator1())
  6. Implement the Composite Design Pattern

    main

    The Composite pattern lets you compose objects into tree structures and then work with these structures as if they were individual objects. It consists of three main parts:

    1. Component: The base class declaring common operations for both simple (Leaf) and complex (Composite) objects. It can optionally manage parent-child relationships.
    2. Leaf: Represents end objects that cannot have children. They perform the actual work.
    3. Composite: Represents complex components that can have children. They typically delegate work to their children and aggregate the results.

    By declaring child-management methods (like add and remove) in the Component base class, client code can treat all objects uniformly without needing to know if they are leaves or composites.

    from src.Composite.Conceptual.main import Component, Leaf, Composite
    
    # Create a leaf
    leaf = Leaf()
    
    # Create a composite and build a tree
    tree = Composite()
    branch = Composite()
    branch.add(Leaf())
    branch.add(Leaf())
    tree.add(branch)
    
    # Work with the tree as if it were a single object
    print(tree.operation())
  7. Implement the State Design Pattern

    main

    The State pattern allows an object to alter its behavior when its internal state changes, making it appear as if the object changed its class.

    To implement this pattern, you need three main components:

    1. Context: Maintains a reference to a State instance representing the current state and delegates requests to it.
    2. State (Abstract Base Class): Declares the interface for all concrete states and provides a backreference to the Context to allow states to trigger transitions.
    3. ConcreteState: Implements specific behaviors associated with a particular state of the Context and can trigger transitions to other states via the Context object.
    from src.State.Conceptual.main import Context, ConcreteStateA
    
    # Initialize context with an initial state
    context = Context(ConcreteStateA())
    
    # Behavior changes based on the current state
    context.request1()
    context.request2()
  8. Implement the Chain of Responsibility pattern

    main

    The Chain of Responsibility pattern allows you to pass requests along a chain of handlers. Each handler decides whether to process the request or pass it to the next handler in the chain.

    To implement this, you can use the following components:

    1. Handler (Interface): Defines the methods set_next(handler) and handle(request).
    2. AbstractHandler (Base Class): Provides default chaining behavior. It implements set_next to link handlers and handle to pass requests to the next handler if the current one cannot process them.
    3. Concrete Handlers: Classes that inherit from AbstractHandler and implement specific logic in their handle method. If they cannot handle the request, they call super().handle(request) to pass it down the chain.

    Fluent Interface Tip: The set_next method returns the next handler, allowing you to link handlers in a single line: handler1.set_next(handler2).set_next(handler3).

    from src.ChainOfResponsibility.Conceptual.main import MonkeyHandler, SquirrelHandler, DogHandler
    
    # Initialize handlers
    monkey = MonkeyHandler()
    squirrel = SquirrelHandler()
    dog = DogHandler()
    
    # Link them in a chain using the fluent interface
    monkey.set_next(squirrel).set_next(dog)
    
    # Send a request to the start of the chain
    result = monkey.handle("Banana")
    print(result)  # Output: Monkey: I'll eat the Banana
  9. Implement the Decorator Design Pattern

    main

    The Decorator pattern allows you to attach new behaviors to objects by placing these objects inside special wrapper objects that contain the behaviors. This enables dynamic extension of functionality without modifying the original class.

    Core Components

    • Component: The base interface that defines operations that can be altered by decorators.
    • ConcreteComponent: Provides the default implementation of the operations.
    • Decorator: The base class for all decorators. It follows the same interface as the Component and maintains a reference to a wrapped Component object. It delegates all work to the wrapped component.
    • ConcreteDecorator: Classes that call the wrapped object and alter its result in some way (either before or after the call).

    Usage Pattern

    Client code should work with the Component interface to remain independent of concrete classes. Decorators can wrap both simple components and other decorators, allowing for multiple layers of behavior.

    from src.Decorator.Conceptual.main import ConcreteComponent, ConcreteDecoratorA, ConcreteDecoratorB
    
    # 1. Create a simple component
    simple = ConcreteComponent()
    
    # 2. Wrap it with decorators (stacking behaviors)
    decorator1 = ConcreteDecoratorA(simple)
    decorator2 = ConcreteDecoratorB(decorator1)
    
    # 3. Use the decorated object
    print(f"RESULT: {decorator2.operation()}")
    # Output: RESULT: ConcreteDecoratorB(ConcreteDecoratorA(ConcreteComponent))
  10. Implement the Command Design Pattern

    main

    The Command pattern turns a request into a stand-alone object that contains all information about the request. This allows you to parameterize methods with different requests, delay or queue a request's execution, and support undoable operations.

    To implement this pattern, you need four components:

    1. Command Interface: An abstract base class defining the execute() method.
    2. Concrete Commands: Classes that implement execute(). They can either perform simple operations themselves (like SimpleCommand) or delegate complex logic to a Receiver (like ComplexCommand).
    3. Receiver: The class that contains the actual business logic and knows how to perform the operations requested by the commands.
    4. Invoker: The class that triggers the command. It does not depend on concrete command or receiver classes; it simply calls execute() on the command objects it holds.
    from abc import ABC, abstractmethod
    
    # 1. Command Interface
    class Command(ABC):
        @abstractmethod
        def execute(self) -> None:
            pass
    
    # 2. Concrete Command (Simple)
    class SimpleCommand(Command):
        def __init__(self, payload: str) -> None:
            self._payload = payload
    
        def execute(self) -> None:
            print(f"SimpleCommand: {self._payload}")
    
    # 2. Concrete Command (Complex with Receiver)
    class ComplexCommand(Command):
        def __init__(self, receiver: Receiver, a: str, b: str) -> None:
            self._receiver = receiver
            self._a = a
            self._b = b
    
        def execute(self) -> None:
            self._receiver.do_something(self._a)
            self._receiver.do_something_else(self._b)
    
    # 3. Receiver
    class Receiver:
        def do_something(self, a: str) -> None:
            print(f"Receiver: Working on {a}")
    
        def do_something_else(self, b: str) -> None:
            print(f"Receiver: Also working on {b}")
    
    # 4. Invoker
    class Invoker:
        def __init__(self) -> None:
            self._on_start = None
            self._on_finish = None
    
        def set_on_start(self, command: Command):
            self._on_start = command
    
        def set_on_finish(self, command: Command):
            self._on_finish = command
    
        def do_something_important(self) -> None:
            if isinstance(self._on_start, Command):
                self._on_start.execute()
            
            print("Invoker: Doing something important...")
    
            if isinstance(self._on_finish, Command):
                self._on_finish.execute()
  11. How the Flyweight pattern works

    main

    The Flyweight pattern optimizes memory usage by splitting an object's state into two parts:

    1. Intrinsic State: Stored within the Flyweight object itself. This state is constant and shared across many entities (e.g., a car's brand and color).
    2. Extrinsic State: Stored by the client or calculated on the fly, then passed to the Flyweight during method calls (e.g., a car's license plate and owner).

    By using a FlyweightFactory, the application ensures that only one instance of each unique intrinsic state exists in memory. When a client needs to represent a new entity, it requests a flyweight from the factory using the intrinsic state and then provides the extrinsic state to perform operations.

    # Client logic example
    # 1. Factory manages shared (intrinsic) state
    factory = FlyweightFactory([["BMW", "M5", "red"]])
    
    # 2. Client provides unique (extrinsic) state during operation
    flyweight = factory.get_flyweight(["BMW", "M5", "red"])
    flyweight.operation(["CL234IR", "James Doe"])