# Cheat Sheet #day12 - Design Patterns

### Design Patterns Cheat Sheet

Design patterns are proven solutions to common problems in software design. They are templates designed to help developers solve recurring problems and create more flexible and reusable code. Here's a quick reference to some of the most widely used design patterns, categorized into three groups: Creational, Structural, and Behavioral.

#### Creational Patterns

1. **Singleton**
   - **Purpose**: Ensure a class has only one instance and provide a global point of access to it.
   - **Use Case**: Configuration classes, logging, thread pools.
   - **Example**:
     ```python
     class Singleton:
         _instance = None

         def __new__(cls, *args, **kwargs):
             if not cls._instance:
                 cls._instance = super().__new__(cls, *args, **kwargs)
             return cls._instance
     ```

2. **Factory Method**
   - **Purpose**: Define an interface for creating an object but let subclasses alter the type of objects that will be created.
   - **Use Case**: Creating objects without specifying the exact class.
   - **Example**:
     ```python
     class Creator:
         def factory_method(self):
             raise NotImplementedError

     class ConcreteCreator(Creator):
         def factory_method(self):
             return ConcreteProduct()
     ```

3. **Abstract Factory**
   - **Purpose**: Provide an interface for creating families of related or dependent objects without specifying their concrete classes.
   - **Use Case**: Creating related objects without specifying their classes.
   - **Example**:
     ```python
     class AbstractFactory:
         def create_product_a(self):
             pass
         def create_product_b(self):
             pass

     class ConcreteFactory1(AbstractFactory):
         def create_product_a(self):
             return ProductA1()
         def create_product_b(self):
             return ProductB1()
     ```

4. **Builder**
   - **Purpose**: Separate the construction of a complex object from its representation, allowing the same construction process to create various representations.
   - **Use Case**: Constructing complex objects step by step.
   - **Example**:
     ```python
     class Builder:
         def build_part(self):
             pass

     class ConcreteBuilder(Builder):
         def build_part(self):
             return Product()
     ```

5. **Prototype**
   - **Purpose**: Specify the kinds of objects to create using a prototypical instance, and create new objects by copying this prototype.
   - **Use Case**: When the cost of creating a new object is expensive.
   - **Example**:
     ```python
     import copy

     class Prototype:
         def clone(self):
             return copy.deepcopy(self)
     ```

#### Structural Patterns

1. **Adapter**
   - **Purpose**: Convert the interface of a class into another interface that clients expect.
   - **Use Case**: Integrating new functionality with legacy code.
   - **Example**:
     ```python
     class Target:
         def request(self):
             pass

     class Adaptee:
         def specific_request(self):
             pass

     class Adapter(Target):
         def __init__(self, adaptee):
             self.adaptee = adaptee

         def request(self):
             self.adaptee.specific_request()
     ```

2. **Bridge**
   - **Purpose**: Separate an object’s interface from its implementation so the two can vary independently.
   - **Use Case**: Avoiding permanent binding between abstraction and implementation.
   - **Example**:
     ```python
     class Abstraction:
         def __init__(self, implementor):
             self.implementor = implementor

         def operation(self):
             self.implementor.operation_impl()

     class Implementor:
         def operation_impl(self):
             pass
     ```

3. **Composite**
   - **Purpose**: Compose objects into tree structures to represent part-whole hierarchies.
   - **Use Case**: Treating individual objects and compositions of objects uniformly.
   - **Example**:
     ```python
     class Component:
         def operation(self):
             pass

     class Composite(Component):
         def __init__(self):
             self.children = []

         def add(self, component):
             self.children.append(component)

         def operation(self):
             for child in self.children:
                 child.operation()
     ```

4. **Decorator**
   - **Purpose**: Attach additional responsibilities to an object dynamically.
   - **Use Case**: Adding behavior to objects at runtime.
   - **Example**:
     ```python
     class Component:
         def operation(self):
             pass

     class Decorator(Component):
         def __init__(self, component):
             self.component = component

         def operation(self):
             self.component.operation()

     class ConcreteDecorator(Decorator):
         def operation(self):
             super().operation()
             # Additional behavior
     ```

5. **Facade**
   - **Purpose**: Provide a simplified interface to a complex subsystem.
   - **Use Case**: Simplifying interactions with complex systems.
   - **Example**:
     ```python
     class Facade:
         def __init__(self):
             self.subsystem1 = Subsystem1()
             self.subsystem2 = Subsystem2()

         def operation(self):
             self.subsystem1.operation1()
             self.subsystem2.operation2()
     ```

6. **Flyweight**
   - **Purpose**: Use sharing to support a large number of fine-grained objects efficiently.
   - **Use Case**: Reducing memory usage with a large number of similar objects.
   - **Example**:
     ```python
     class Flyweight:
         def __init__(self, intrinsic_state):
             self.intrinsic_state = intrinsic_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]
     ```

7. **Proxy**
   - **Purpose**: Provide a surrogate or placeholder for another object to control access to it.
   - **Use Case**: Controlling access to objects, lazy initialization, access control.
   - **Example**:
     ```python
     class Subject:
         def request(self):
             pass

     class RealSubject(Subject):
         def request(self):
             pass

     class Proxy(Subject):
         def __init__(self, real_subject):
             self.real_subject = real_subject

         def request(self):
             # Access control or additional behavior
             self.real_subject.request()
     ```

#### Behavioral Patterns

1. **Chain of Responsibility**
   - **Purpose**: Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request.
   - **Use Case**: Decoupling request senders and receivers.
   - **Example**:
     ```python
     class Handler:
         def __init__(self, successor=None):
             self.successor = successor

         def handle_request(self, request):
             if self.successor:
                 self.successor.handle_request(request)
     ```

2. **Command**
   - **Purpose**: Encapsulate a request as an object, thereby allowing parameterization of clients with queues, requests, and operations.
   - **Use Case**: Implementing undo/redo functionality.
   - **Example**:
     ```python
     class Command:
         def execute(self):
             pass

     class ConcreteCommand(Command):
         def __init__(self, receiver):
             self.receiver = receiver

         def execute(self):
             self.receiver.action()

     class Receiver:
         def action(self):
             pass
     ```

3. **Interpreter**
   - **Purpose**: Define a grammar for a language and provide an interpreter to deal with this grammar.
   - **Use Case**: Parsing and interpreting expressions.
   - **Example**:
     ```python
     class AbstractExpression:
         def interpret(self, context):
             pass

     class TerminalExpression(AbstractExpression):
         def interpret(self, context):
             pass

     class NonterminalExpression(AbstractExpression):
         def __init__(self, expressions):
             self.expressions = expressions

         def interpret(self, context):
             for expr in self.expressions:
                 expr.interpret(context)
     ```

4. **Iterator**
   - **Purpose**: Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation.
   - **Use Case**: Traversing collections like lists and trees.
   - **Example**:
     ```python
     class Iterator:
         def __init__(self, collection):
             self.collection = collection
             self.index = 0

         def has_next(self):
             return self.index < len(self.collection)

         def next(self):
             item = self.collection[self.index]
             self.index += 1
             return item
     ```

5. **Mediator**
   - **Purpose**: Define an object that encapsulates how a set of objects interact, promoting loose coupling.
   - **Use Case**: Reducing complexity in communication between multiple objects.
   - **Example**:
     ```python
     class Mediator:
         def notify(self, sender, event):
             pass

     class ConcreteMediator(Mediator):
         def notify(self, sender, event):
             pass

     class Colleague:
         def __init__(self, mediator):
             self.mediator = mediator

         def notify_mediator(self, event):
             self.mediator.notify(self, event)
     ```

6. **Memento**
   - **Purpose**: Capture and externalize an object's internal state so that it can be restored later without violating encapsulation.
   - **Use Case**: Implementing undo/redo functionality.
   - **Example**:
     ```python


     class Memento:
         def __init__(self, state):
             self.state = state

     class Originator:
         def set_memento(self, memento):
             self.state = memento.state

         def create_memento(self):
             return Memento(self.state)
     ```

7. **Observer**
   - **Purpose**: Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.
   - **Use Case**: Implementing event handling systems.
   - **Example**:
     ```python
     class Subject:
         def __init__(self):
             self.observers = []

         def attach(self, observer):
             self.observers.append(observer)

         def notify(self):
             for observer in self.observers:
                 observer.update()

     class Observer:
         def update(self):
             pass
     ```

8. **State**
   - **Purpose**: Allow an object to alter its behavior when its internal state changes, appearing to change its class.
   - **Use Case**: Implementing state machines.
   - **Example**:
     ```python
     class State:
         def handle(self, context):
             pass

     class ConcreteStateA(State):
         def handle(self, context):
             context.state = ConcreteStateB()

     class Context:
         def __init__(self, state):
             self.state = state

         def request(self):
             self.state.handle(self)
     ```

9. **Strategy**
   - **Purpose**: Define a family of algorithms, encapsulate each one, and make them interchangeable.
   - **Use Case**: Implementing interchangeable algorithms or behaviors.
   - **Example**:
     ```python
     class Strategy:
         def execute(self):
             pass

     class ConcreteStrategyA(Strategy):
         def execute(self):
             pass

     class Context:
         def __init__(self, strategy):
             self.strategy = strategy

         def execute_strategy(self):
             self.strategy.execute()
     ```

10. **Template Method**
    - **Purpose**: Define the skeleton of an algorithm in an operation, deferring some steps to subclasses.
    - **Use Case**: Implementing invariant parts of an algorithm once and allowing subclasses to refine variable parts.
    - **Example**:
      ```python
      class AbstractClass:
          def template_method(self):
              self.step1()
              self.step2()

          def step1(self):
              pass

          def step2(self):
              pass

      class ConcreteClass(AbstractClass):
          def step1(self):
              pass
          def step2(self):
              pass
      ```

11. **Visitor**
    - **Purpose**: Represent an operation to be performed on the elements of an object structure without changing the classes of the elements.
    - **Use Case**: Adding operations to class hierarchies without modifying the classes.
    - **Example**:
      ```python
      class Visitor:
          def visit(self, element):
              pass

      class ConcreteVisitor(Visitor):
          def visit(self, element):
              pass

      class Element:
          def accept(self, visitor):
              visitor.visit(self)
      ```

### Summary

- **Creational Patterns**: Focus on object creation mechanisms.
- **Structural Patterns**: Focus on object composition or structure.
- **Behavioral Patterns**: Focus on object interaction and responsibility.

This cheat sheet provides a high-level overview of the most common design patterns. For deeper understanding and practical implementation, refer to resources like the `Design Patterns: Elements of Reusable Object-Oriented Software` book (the `Gang of Four` book) and other detailed design pattern references.
