Design Patterns Reference

Design patterns reference — Singleton, Factory, Observer, Strategy, Decorator, Command. Every GoF pattern with intent, structure, and when to use it. Interview prep.

17 min read

This cheatsheet provides quick access to common design patterns and their implementations in various programming languages. It’s your go-to reference when you need to solve recurring software design problems.

Installation

Design patterns are a conceptual tool, not a software package. No installation is required.

Core Concepts

Design patterns are general, reusable solutions to commonly occurring problems within a given context in software design. They are not finished designs that can be directly translated into code, but rather descriptions or templates for how to solve a problem that can be used in many different situations.

Key characteristics of design patterns:

  • Proven Solutions: They represent best practices that have been refined over time.
  • Abstract: They describe a problem and a solution at a higher level than specific code.
  • Reusable: They can be applied to different problems and in different contexts.
  • Communicative: They provide a common vocabulary for developers to discuss solutions.

Commands / Usage

This section outlines common design patterns and provides illustrative examples. The specific implementation details will vary greatly by programming language.

Creational Patterns

These patterns deal with object creation mechanisms, trying to create objects in a manner suitable to the situation.

Singleton

Ensures a class only has one instance and provides a global point of access to it.

  • Java Example:

    public class Singleton {
        private static Singleton instance;
    
        private Singleton() {} // Private constructor
    
        public static Singleton getInstance() {
            if (instance == null) {
                instance = new Singleton();
            }
            return instance;
        }
    }
    

    Ensures only one instance of Singleton class is created.

  • Python Example:

    class Singleton:
        _instance = None
    
        def __new__(cls):
            if cls._instance is None:
                cls._instance = super(Singleton, cls).__new__(cls)
                # Initialize other attributes here if needed
            return cls._instance
    
    # Usage
    s1 = Singleton()
    s2 = Singleton()
    print(s1 is s2) # Output: True
    

    Creates a single instance of the Singleton class.

Factory Method

Defines an interface for creating an object, but lets subclasses decide which class to instantiate.

  • Python Example:

    class Creator:
        def factory_method(self):
            raise NotImplementedError
    
        def some_operation(self):
            product = self.factory_method()
            return f"Creator: The same creator's code has just worked with {product.operation()}"
    
    class ConcreteCreatorA(Creator):
        def factory_method(self):
            return ConcreteProductA()
    
    class ConcreteCreatorB(Creator):
        def factory_method(self):
            return ConcreteProductB()
    
    class Product:
        def operation(self):
            pass
    
    class ConcreteProductA(Product):
        def operation(self):
            return "{Result of the ConcreteProductA}"
    
    class ConcreteProductB(Product):
        def operation(self):
            return "{Result of the ConcreteProductB}"
    
    # Usage
    print("Client: I'm going to use ConcreteCreatorA:")
    creator_a = ConcreteCreatorA()
    print(creator_a.some_operation())
    
    print("\nClient: I'm going to use ConcreteCreatorB:")
    creator_b = ConcreteCreatorB()
    print(creator_a.some_operation())
    

    Allows subclasses to decide which concrete product to create.

Abstract Factory

Provides an interface for creating families of related or dependent objects without specifying their concrete classes.

  • Python Example:

    class AbstractFactory:
        def create_product_a(self):
            pass
    
        def create_product_b(self):
            pass
    
    class ConcreteFactory1(AbstractFactory):
        def create_product_a(self):
            return ConcreteProductA1()
    
        def create_product_b(self):
            return ConcreteProductB1()
    
    class ConcreteFactory2(AbstractFactory):
        def create_product_a(self):
            return ConcreteProductA2()
    
        def create_product_b(self):
            return ConcreteProductB2()
    
    class ProductA:
        pass
    
    class ProductB:
        pass
    
    class ConcreteProductA1(ProductA):
        pass
    
    class ConcreteProductB1(ProductB):
        pass
    
    class ConcreteProductA2(ProductA):
        pass
    
    class ConcreteProductB2(ProductB):
        pass
    
    def client_code(factory: AbstractFactory):
        product_a = factory.create_product_a()
        product_b = factory.create_product_b()
        print(f"Created {type(product_a).__name__} and {type(product_b).__name__}")
    
    # Usage
    print("Client: Creating products with ConcreteFactory1:")
    client_code(ConcreteFactory1())
    
    print("\nClient: Creating products with ConcreteFactory2:")
    client_code(ConcreteFactory2())
    

    Creates families of objects without exposing their concrete classes.

Builder

Separates the construction of a complex object from its representation so that the same construction process can create different representations.

  • Python Example:

    class Builder:
        def reset(self):
            pass
    
        def build_part_a(self):
            pass
    
        def build_part_b(self):
            pass
    
        def build_part_c(self):
            pass
    
    class ConcreteBuilder(Builder):
        def __init__(self):
            self.product = Product()
    
        def reset(self):
            self.product = Product()
    
        def build_part_a(self):
            self.product.add("PartA")
    
        def build_part_b(self):
            self.product.add("PartB")
    
        def build_part_c(self):
            self.product.add("PartC")
    
        def get_product(self):
            product = self.product
            self.reset() # Reset for next build
            return product
    
    class Product:
        def __init__(self):
            self.parts = []
    
        def add(self, part):
            self.parts.append(part)
    
        def list_parts(self):
            print(f"Product parts: {', '.join(self.parts)}")
    
    class Director:
        def __init__(self):
            self._builder = None
    
        def set_builder(self, builder: Builder):
            self._builder = builder
    
        def build_minimal_product(self):
            self._builder.build_part_a()
    
        def build_full_product(self):
            self._builder.build_part_a()
            self._builder.build_part_b()
            self._builder.build_part_c()
    
    # Usage
    builder = ConcreteBuilder()
    director = Director()
    director.set_builder(builder)
    
    print("Standard basic product:")
    director.build_minimal_product()
    builder.get_product().list_parts()
    
    print("\nStandard full product:")
    director.build_full_product()
    builder.get_product().list_parts()
    
    print("\nCustom product:")
    builder.build_part_a()
    builder.build_part_c()
    builder.get_product().list_parts()
    

    Builds complex objects step by step, allowing different representations.

Prototype

Specifies the kinds of objects that can be created using a cloned prototype, and creates new objects by copying this prototype.

  • Python Example:

    import copy
    
    class Prototype:
        def clone(self):
            pass
    
    class ConcretePrototypeA(Prototype):
        def __init__(self, value):
            self.value = value
    
        def clone(self):
            return copy.deepcopy(self) # Deep copy for independent object
    
    class ConcretePrototypeB(Prototype):
        def __init__(self, value, data):
            self.value = value
            self.data = data
    
        def clone(self):
            return copy.deepcopy(self)
    
    # Usage
    prototype_a = ConcretePrototypeA("Original A")
    cloned_a = prototype_a.clone()
    print(f"Original A: {prototype_a.value}, Cloned A: {cloned_a.value}")
    cloned_a.value = "Modified A"
    print(f"Original A after modification: {prototype_a.value}, Cloned A after modification: {cloned_a.value}")
    
    prototype_b = ConcretePrototypeB("Original B", [1, 2, 3])
    cloned_b = prototype_b.clone()
    print(f"Original B: {prototype_b.value}, Cloned B: {cloned_b.value}")
    cloned_b.value = "Modified B"
    cloned_b.data.append(4)
    print(f"Original B after modification: {prototype_b.value}, {prototype_b.data}")
    print(f"Cloned B after modification: {cloned_b.value}, {cloned_b.data}")
    

    Creates new objects by copying an existing object (prototype).

Structural Patterns

These patterns are concerned with class and object composition. They explain how to assemble objects into larger structures.

Adapter

Converts the interface of a class into another interface clients expect. Adapter lets classes work together that couldn’t otherwise because of incompatible interfaces.

  • Python Example:

    class Target:
        def request(self):
            return "Target: The default target's behavior."
    
    class Adaptee:
        def specific_request(self):
            return "+Adaptee: The specific request."
    
    class Adapter(Target):
        def __init__(self, adaptee: Adaptee):
            self._adaptee = adaptee
    
        def request(self):
            return f"Adapter: (TRANSLATED) {self._adaptee.specific_request()}"
    
    # Usage
    adaptee = Adaptee()
    adapter = Adapter(adaptee)
    print(adapter.request())
    

    Allows objects with incompatible interfaces to collaborate.

Bridge

Decouples an abstraction from its implementation so that the two can vary independently.

  • Python Example:

    class Abstraction:
        def __init__(self, implementor):
            self._implementor = implementor
    
        def operation(self):
            return self._implementor.operation_impl()
    
    class RefinedAbstraction(Abstraction):
        def operation(self):
            return f"Refined Abstraction: {self._implementor.operation_impl()} (and more)"
    
    class Implementor:
        def operation_impl(self):
            pass
    
    class ConcreteImplementorA(Implementor):
        def operation_impl(self):
            return "Concrete Implementor A"
    
    class ConcreteImplementorB(Implementor):
        def operation_impl(self):
            return "Concrete Implementor B"
    
    # Usage
    impl_a = ConcreteImplementorA()
    abstraction_a = Abstraction(impl_a)
    print(abstraction_a.operation())
    
    impl_b = ConcreteImplementorB()
    refined_abstraction_b = RefinedAbstraction(impl_b)
    print(refined_abstraction_b.operation())
    

    Separates interface from implementation.

Composite

Composes objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly.

  • Python Example:

    class Component:
        def __init__(self, name):
            self.name = name
    
        def display(self, indent=""):
            pass
    
    class Leaf(Component):
        def display(self, indent=""):
            print(f"{indent}- {self.name} (Leaf)")
    
    class Composite(Component):
        def __init__(self, name):
            super().__init__(name)
            self.children = []
    
        def add(self, component):
            self.children.append(component)
    
        def remove(self, component):
            self.children.remove(component)
    
        def display(self, indent=""):
            print(f"{indent}+ {self.name} (Composite)")
            for child in self.children:
                child.display(indent + "  ")
    
    # Usage
    root = Composite("Root")
    branch1 = Composite("Branch 1")
    leaf1 = Leaf("Leaf 1")
    leaf2 = Leaf("Leaf 2")
    branch2 = Composite("Branch 2")
    leaf3 = Leaf("Leaf 3")
    
    branch1.add(leaf1)
    branch1.add(leaf2)
    branch2.add(leaf3)
    root.add(branch1)
    root.add(branch2)
    
    root.display()
    

    Treats individual objects and compositions uniformly.

Decorator

Attaches additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.

  • Python Example:

    class Component:
        def operation(self):
            pass
    
    class ConcreteComponent(Component):
        def operation(self):
            return "ConcreteComponent"
    
    class Decorator(Component):
        def __init__(self, component: Component):
            self._component = component
    
        def operation(self):
            return self._component.operation()
    
    class ConcreteDecoratorA(Decorator):
        def operation(self):
            return f"ConcreteDecoratorA({self._component.operation()})"
    
    class ConcreteDecoratorB(Decorator):
        def operation(self):
            return f"ConcreteDecoratorB({self._component.operation()})"
    
    # Usage
    simple_component = ConcreteComponent()
    print(f"Client: I've got a simple component:\n{simple_component.operation()}")
    
    decorator1 = ConcreteDecoratorA(simple_component)
    decorator2 = ConcreteDecoratorB(decorator1)
    print(f"\nClient: Now I've got a decorated component:\n{decorator2.operation()}")
    

    Adds responsibilities to objects dynamically without subclassing.

Facade

Provides a unified interface to a set of interfaces in a subsystem. Facade defines a higher-level interface that makes the subsystem easier to use.

  • Python Example:

    class SubsystemA:
        def operation_a(self):
            return "Subsystem A operation."
    
    class SubsystemB:
        def operation_b(self):
            return "Subsystem B operation."
    
    class Facade:
        def __init__(self, subsystem_a: SubsystemA, subsystem_b: SubsystemB):
            self._subsystem_a = subsystem_a
            self._subsystem_b = subsystem_b
    
        def do_something_complex(self):
            result_a = self._subsystem_a.operation_a()
            result_b = self._subsystem_b.operation_b()
            return f"Facade combining: {result_a} and {result_b}"
    
    # Usage
    sub_a = SubsystemA()
    sub_b = SubsystemB()
    facade = Facade(sub_a, sub_b)
    print(facade.do_something_complex())
    

    Provides a simplified interface to a complex subsystem.

Flyweight

Uses sharing to support large numbers of fine-grained objects efficiently.

  • Python Example:

    class Flyweight:
        def __init__(self, intrinsic_state):
            self.intrinsic_state = intrinsic_state
    
        def operation(self, extrinsic_state):
            print(f"Flyweight: {self.intrinsic_state} - Extrinsic: {extrinsic_state}")
    
    class FlyweightFactory:
        def __init__(self):
            self._flyweights = {}
    
        def get_flyweight(self, key):
            if key not in self._flyweights:
                self._flyweights[key] = Flyweight(key)
            return self._flyweights[key]
    
    # Usage
    factory = FlyweightFactory()
    flyweight1 = factory.get_flyweight("shared_state_1")
    flyweight1.operation("extrinsic_state_A")
    
    flyweight2 = factory.get_flyweight("shared_state_2")
    flyweight2.operation("extrinsic_state_B")
    
    # Demonstrating sharing
    flyweight3 = factory.get_flyweight("shared_state_1")
    flyweight3.operation("extrinsic_state_C")
    
    print(f"flyweight1 is flyweight3: {flyweight1 is flyweight3}")
    

    Manages a pool of shared objects to reduce memory usage.

Proxy

A surrogate or placeholder for another object to control access to it.

  • Python Example:

    class Subject:
        def request(self):
            pass
    
    class RealSubject(Subject):
        def request(self):
            return "RealSubject: Handling request."
    
    class Proxy(Subject):
        def __init__(self, real_subject: RealSubject):
            self._real_subject = real_subject
    
        def request(self):
            if self._check_access():
                return self._real_subject.request()
            else:
                return "Proxy: Access denied."
    
        def _check_access(self):
            print("Proxy: Checking access...")
            return True # Simulate access check
    
    # Usage
    real_subject = RealSubject()
    proxy = Proxy(real_subject)
    print(proxy.request())
    

    Provides a surrogate or placeholder for another object.

Behavioral Patterns

These patterns are concerned with algorithms and the assignment of responsibilities between objects.

Chain of Responsibility

Avoids coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chains the receiving objects and passes the request along the chain until an object handles it.

  • Python Example:

    class Handler:
        def __init__(self):
            self.next_handler = None
    
        def set_next(self, handler):
            self.next_handler = handler
            return handler
    
        def handle(self, request):
            if self.next_handler:
                return self.next_handler.handle(request)
            return None
    
    class ConcreteHandlerA(Handler):
        def handle(self, request):
            if "A" in request:
                return f"ConcreteHandlerA handled: {request}"
            else:
                return super().handle(request)
    
    class ConcreteHandlerB(Handler):
        def handle(self, request):
            if "B" in request:
                return f"ConcreteHandlerB handled: {request}"
            else:
                return super().handle(request)
    
    # Usage
    handler_a = ConcreteHandlerA()
    handler_b = ConcreteHandlerB()
    
    handler_a.set_next(handler_b)
    
    print("Handling request 'Request A':", handler_a.handle("Request A"))
    print("Handling request 'Request B':", handler_a.handle("Request B"))
    print("Handling request 'Request C':", handler_a.handle("Request C"))
    

    Passes requests along a chain of handlers.

Command

Encapsulates a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations.

  • Python Example:

    class Command:
        def execute(self):
            pass
    
    class Receiver:
        def action(self):
            return "Receiver action."
    
    class ConcreteCommand(Command):
        def __init__(self, receiver: Receiver):
            self._receiver = receiver
    
        def execute(self):
            return f"ConcreteCommand executing: {self._receiver.action()}"
    
    class Invoker:
        def __init__(self):
            self._command = None
    
        def set_command(self, command: Command):
            self._command = command
    
        def run_command(self):
            if self._command:
                return self._command.execute()
            return "No command set."
    
    # Usage
    receiver = Receiver()
    command = ConcreteCommand(receiver)
    invoker = Invoker()
    invoker.set_command(command)
    print(invoker.run_command())
    

    Encapsulates a request as an object.

Interpreter

Given a language, defines a representation for its grammar along with an interpreter that uses the representation to interpret sentences in the language.

  • Python Example (Simplified):

    class Expression:
        def interpret(self):
            pass
    
    class TerminalExpression(Expression):
        def __init__(self, name):
            self.name = name
    
        def interpret(self):
            return self.name
    
    class NonTerminalExpression(Expression):
        def __init__(self, expression1: Expression, expression2: Expression):
            self._expression1 = expression1
            self._expression2 = expression2
    
        def interpret(self):
            return f"({self._expression1.interpret()} + {self._expression2.interpret()})"
    
    # Usage (representing a simple grammar like (A + B))
    expr1 = TerminalExpression("A")
    expr2 = TerminalExpression("B")
    non_terminal = NonTerminalExpression(expr1, expr2)
    print(non_terminal.interpret())
    

    Defines a representation for a grammar and an interpreter for that grammar.

Iterator

Provides a way to access the elements of an aggregate object sequentially without exposing its underlying representation.

  • Python Example:

    class Iterator:
        def __init__(self, collection):
            self._collection = collection
            self._index = 0
    
        def __iter__(self):
            return self
    
        def __next__(self):
            if self._index < len(self._collection):
                item = self._collection[self._index]
                self._index += 1
                return item
            else:
                raise StopIteration
    
    class Collection:
        def __init__(self, items):
            self._items = items
    
        def __iter__(self):
            return Iterator(self._items)
    
    # Usage
    my_collection = Collection(["apple", "banana", "cherry"])
    for item in my_collection:
        print(item)
    

    Provides sequential access to elements of a collection.

Mediator

Defines an object that encapsulates how a set of objects interact. Mediator promotes loose coupling by keeping objects from referring to each other explicitly, and it lets you vary their interaction independently.

  • Python Example:

    class Mediator:
        def notify(self, sender, event):
            pass
    
    class Colleague:
        def __init__(self, mediator: Mediator):
            self._mediator = mediator
    
        def send(self, event):
            self._mediator.notify(self, event)
    
    class ConcreteColleagueA(Colleague):
        def receive(self, event):
            print(f"ConcreteColleagueA received event: {event}")
    
    class ConcreteColleagueB(Colleague):
        def receive(self, event):
            print(f"ConcreteColleagueB received event: {event}")
    
    class ConcreteMediator(Mediator):
        def __init__(self):
            self.colleague_a = None
            self.colleague_b = None
    
        def set_colleague_a(self, colleague_a: ConcreteColleagueA):
            self.colleague_a = colleague_a
    
        def set_colleague_b(self, colleague_b: ConcreteColleagueB):
            self.colleague_b = colleague_b
    
        def notify(self, sender, event):
            if sender is self.colleague_a:
                self.colleague_b.receive(f"Mediated event from A: {event}")
            elif sender is self.colleague_b:
                self.colleague_a.receive(f"Mediated event from B: {event}")
    
    # Usage
    mediator = ConcreteMediator()
    colleague_a = ConcreteColleagueA(mediator)
    colleague_b = ConcreteColleagueB(mediator)
    
    mediator.set_colleague_a(colleague_a)
    mediator.set_colleague_b(colleague_b)
    
    colleague_a.send("Hello from A")
    colleague_b.send("Hi from B")
    

    Mediates interactions between objects.

Memento

Without violating encapsulation, capture and externalize an object’s internal state so that the object can be restored to this state later.

  • Python Example:

    class Memento:
        def __init__(self, state):
            self._state = state
    
        def get_state(self):
            return self._state
    
    class Originator:
        def __init__(self):
            self._state = None
    
        def set_state(self, state):
            self._state = state
            print(f"Originator: Setting state to {self._state}")
    
        def save_to_memento(self):
            print("Originator: Saving to Memento.")
            return Memento(self._state)
    
        def restore_from_memento(self, memento: Memento):
            self._state = memento.get_state()
            print(f"Originator: Restored from Memento. State is {self._state}")
    
    class Caretaker:
        def __init__(self):
            self._memento = None
    
        def save(self, originator: Originator):
            print("Caretaker: Saving Originator's state.")
            self._memento = originator.save_to_memento()
    
        def restore(self, originator: Originator):
            print("Caretaker: Restoring Originator's state.")
            if self._memento:
                originator.restore_from_memento(self._memento)
    
    # Usage
    originator = Originator()
    caretaker = Caretaker()
    
    originator.set_state("State #1")
    caretaker.save(originator)
    
    originator.set_state("State #2") # Change state
    originator.set_state("State #3")
    
    caretaker.restore(originator)
    

    Captures and restores an object’s internal state.

Observer

Defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.

  • Python Example:

    class Subject:
        def __init__(self):
            self._observers = []
    
        def attach(self, observer):
            self._observers.append(observer)
    
        def detach(self, observer):
            self._observers.remove(observer)
    
        def notify(self, event):
            for observer in self._observers:
                observer.update(self, event)
    
    class ConcreteSubject(Subject):
        def do_something(self):
            print("ConcreteSubject: Doing something and notifying observers.")
            self.notify("Something happened")
    
    class Observer:
        def update(self, subject, event):
            pass
    
    class ConcreteObserverA(Observer):
        def update(self, subject, event):
            print(f"ConcreteObserverA received update: {event} from {type(subject).__name__}")
    
    class ConcreteObserverB(Observer):
        def update(self, subject, event):
            print(f"ConcreteObserverB received update: {event} from {type(subject).__name__}")
    
    # Usage
    subject = ConcreteSubject()
    observer_a = ConcreteObserverA()
    observer_b = ConcreteObserverB()
    
    subject.attach(observer_a)
    subject.attach(observer_b)
    
    subject.do_something()
    
    subject.detach(observer_a)
    subject.do_something()
    

    Defines a publish-subscribe mechanism.

State

Allows an object to alter its behavior when its internal state changes. The object will appear to change its class.

  • Python Example:

    class State:
        def handle(self):
            pass
    
    class ConcreteStateA(State):
        def handle(self):
            return "ConcreteStateA handling."
    
    class ConcreteStateB(State):
        def handle(self):
            return "ConcreteStateB handling."
    
    class Context:
        def __init__(self):
            self._state = None
    
        def set_state(self, state: State):
            self._state = state
            print(f"Context: State changed to {type(self._state).__name__}")
    
        def request(self):
            return f"Context: {self._state.handle()}"
    
    # Usage
    context = Context()
    state_a = ConcreteStateA()
    state_b = ConcreteStateB()
    
    context.set_state(state_a)
    print(context.request())
    
    context.set_state(state_b)
    print(context.request())
    

    Allows an object to change its behavior based on its internal state.

Strategy

Defines a family of algorithms, encapsulates each one, and makes them interchangeable. Strategy lets the algorithm vary independently from clients that use it.

  • Python Example:

    class Strategy:
        def execute(self, data):
            pass
    
    class ConcreteStrategyA(Strategy):
        def execute(self, data):
            return f"ConcreteStrategyA: {data} processed."
    
    class ConcreteStrategyB(Strategy):
        def execute(self, data):
            return f"ConcreteStrategyB: {data} processed."
    
    class Context:
        def __init__(self, strategy: Strategy):
            self._strategy = strategy
    
        def set_strategy(self, strategy: Strategy):
            self._strategy = strategy
    
        def execute_strategy(self, data):
            return self._strategy.execute(data)
    
    # Usage
    strategy_a = ConcreteStrategyA()
    context = Context(strategy_a)
    print(context.execute_strategy("some data"))
    
    strategy_b = ConcreteStrategyB()
    context.set_strategy(strategy_b)
    print(context.execute_strategy("other data"))
    

    Defines a family of algorithms and makes them interchangeable.

Template Method

Defines the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm’s structure.

  • Python Example:

    class AbstractClass:
        def template_method(self):
            self.step1()
            self.step2()
            self.step3()
    
        def step1(self):
            raise NotImplementedError
    
        def step2(self):
            pass # Optional step
    
        def step3(self):
            raise NotImplementedError
    
    class ConcreteClassA(AbstractClass):
        def step1(self):
            print("ConcreteClassA: Step 1")
    
        def step3(self):
            print("ConcreteClassA: Step 3")
    
    class ConcreteClassB(AbstractClass):
        def step1(self):
            print("ConcreteClassB: Step 1")
    
        def step2(self):
            print("ConcreteClassB: Step 2 (overridden)")
    
        def step3(self):
            print("ConcreteClassB: Step 3")
    
    # Usage
    print("Running ConcreteClassA:")
    concrete_a = ConcreteClassA()
    concrete_a.template_method()
    
    print("\nRunning ConcreteClassB:")
    concrete_b = ConcreteClassB()
    concrete_b.template_method()
    

    Defines the skeleton of an algorithm, allowing subclasses to fill in specific steps.

Visitor

Represents an operation to be performed on the elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates.

  • Python Example:

    class Visitor:
        def visit_concrete_element_a(self, element):
            pass
    
        def visit_concrete_element_b(self, element):
            pass
    
    class ConcreteVisitor1(Visitor):
        def visit_concrete_element_a(self, element):
            print(f"{element.operation_a()} + ConcreteVisitor1")
    
        def visit_concrete_element_b(self, element):
            print(f"{element.operation_b()} + ConcreteVisitor1")
    
    class ConcreteVisitor2(Visitor):
        def visit_concrete_element_a(self, element):
            print(f"{element.operation_a()} + ConcreteVisitor2")
    
        def visit_concrete_element_b(self, element):
            print(f"{element.operation_b()} + ConcreteVisitor2")
    
    class Element:
        def accept(self, visitor: Visitor):
            pass
    
    class ConcreteElementA(Element):
        def operation_a(self):
            return "ConcreteElementA's operation"
    
        def accept(self, visitor: Visitor):
            visitor.visit_concrete_element_a(self)
    
    class ConcreteElementB(Element):
        def operation_b(self):
            return "ConcreteElementB's operation"
    
        def accept(self, visitor: Visitor):
            visitor.visit_concrete_element_b(self)
    
    # Usage
    elements = [ConcreteElementA(), ConcreteElementB()]
    visitor1 = ConcreteVisitor1()
    visitor2 = ConcreteVisitor2()
    
    print("Visiting elements with Visitor1:")
    for element in elements:
        element.accept(visitor1)
    
    print("\nVisiting elements with Visitor2:")
    for element in elements:
        element.accept(visitor2)
    

    Defines new operations for an object structure without altering the structure itself.

Common Patterns

These are not specific design patterns but rather common ways design patterns are used in conjunction with other tools or techniques.

  • Dependency Injection with Factories: Using a Factory pattern to create objects that are then injected into other objects. This decouples the creation of dependencies.

    # Conceptual Example
    class Service:
        def perform_task(self):
            print("Performing service task.")
    
    class ServiceFactory:
        def create_service(self):
            return Service()
    
    class Client:
        def __init__(self, service_factory: ServiceFactory):
            self.service = service_factory.create_service()
    
        def do_work(self):
            self.service.perform_task()
    
    # Usage
    factory = ServiceFactory()
    client = Client(factory)
    client.do_work()
    
  • State Pattern for Finite State Machines: Implementing a finite state machine using the State pattern, where each state is a concrete class.

    # Conceptual Example
    class State:
        def handle_input(self, context):
            pass
    
    class StateA(State):
        def handle_input(self, context):
            print("Transitioning from A to B")
            context.set_state(StateB())
    
    class StateB(State):
        def handle_input(self, context):
            print("Transitioning from