0% found this document useful (0 votes)
4 views10 pages

Design Patterns

Design patterns are reusable solutions to common software design problems, popularized by the 'Gang of Four' book. The 23 patterns are categorized into Creational, Structural, and Behavioral patterns, each serving specific purposes like object creation, composition, and communication. Understanding these patterns enhances communication among engineers and helps avoid unnecessary complexity in software development.

Uploaded by

Pedro Henrique
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
4 views10 pages

Design Patterns

Design patterns are reusable solutions to common software design problems, popularized by the 'Gang of Four' book. The 23 patterns are categorized into Creational, Structural, and Behavioral patterns, each serving specific purposes like object creation, composition, and communication. Understanding these patterns enhances communication among engineers and helps avoid unnecessary complexity in software development.

Uploaded by

Pedro Henrique
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Design Patterns

Gang of Four Patterns in Modern Software

Programming Guides

1. What Are Design Patterns?

Design patterns are reusable solutions to commonly occurring problems in software design. They were popularized by
the 'Gang of Four' (GoF) book 'Design Patterns: Elements of Reusable Object-Oriented Software' (1994) by Gamma,
Helm, Johnson, and Vlissides.

Patterns are not code you copy-paste - they are templates that describe how to structure relationships between classes
and objects to solve a given problem. They provide a shared vocabulary: saying 'use a Strategy here' communicates an
entire structural idea to any engineer familiar with the pattern.

The 23 GoF patterns are divided into three categories based on their purpose:

Category Purpose Patterns

Creational How objects are created Singleton, Factory, Abstract Factory, Builder, Protot
Structural How objects are composed Adapter, Bridge, Composite, Decorator, Facade, Fly
Behavioral How objects communicate Chain of Resp., Command, Iterator, Mediator, Meme

2. Creational Patterns

Singleton
Ensures a class has only one instance and provides a global access point to it. Use for shared resources like a
database connection pool or a configuration object. Be cautious: Singletons make testing harder (hidden global state)
and can cause concurrency issues if not implemented thread-safely.

class DatabasePool:
_instance = None

def __new__(cls):
if cls._instance is None:

Page 1
cls._instance = super().__new__(cls)
cls._instance._pool = cls._instance._create_pool()
return cls._instance

def _create_pool(self):
return [] # initialize connection pool

# Both variables point to the same object


a = DatabasePool()
b = DatabasePool()
assert a is b # True

Factory Method
Defines an interface for creating an object but lets subclasses decide which class to instantiate. Useful when the exact
type of object to create depends on context or configuration. Decouples creation from usage.

from abc import ABC, abstractmethod

class Notifier(ABC):
@abstractmethod
def send(self, message: str): ...

class EmailNotifier(Notifier):
def send(self, message): print(f'Email: {message}')

class SMSNotifier(Notifier):
def send(self, message): print(f'SMS: {message}')

def get_notifier(channel: str) -> Notifier:


return {'email': EmailNotifier, 'sms': SMSNotifier}[channel]()

notifier = get_notifier('email')
[Link]('Hello!') # Email: Hello!

Builder
Constructs complex objects step by step. Separates the construction process from the final representation. Ideal when
an object has many optional parameters and you want to avoid a constructor with 10+ arguments ('telescoping
constructor').

class QueryBuilder:
def __init__(self, table: str):
self._table = table
self._conditions = []
self._limit = None
self._order = None

def where(self, condition: str):


self._conditions.append(condition)
return self # fluent interface

def order_by(self, column: str):


self._order = column

Page 2
return self

def limit(self, n: int):


self._limit = n
return self

def build(self) -> str:


q = f'SELECT * FROM {self._table}'
if self._conditions:
q += ' WHERE ' + ' AND '.join(self._conditions)
if self._order: q += f' ORDER BY {self._order}'
if self._limit: q += f' LIMIT {self._limit}'
return q

sql = (QueryBuilder('users')
.where('active = true')
.order_by('name')
.limit(10)
.build())

3. Structural Patterns

Adapter
Converts the interface of a class into another interface that clients expect. Useful when integrating third-party libraries or
legacy code that has an incompatible interface. Acts as a wrapper - the client talks to the adapter, which translates calls
to the adaptee.

# Third-party library with an incompatible interface


class LegacyPaymentGateway:
def make_payment(self, amount_cents: int, card_num: str): ...

# Our system expects this interface


class PaymentProcessor(ABC):
@abstractmethod
def charge(self, amount_dollars: float, card: str): ...

# Adapter bridges the gap


class LegacyGatewayAdapter(PaymentProcessor):
def __init__(self, gateway: LegacyPaymentGateway):
self._gateway = gateway

def charge(self, amount_dollars: float, card: str):


cents = int(amount_dollars * 100)
self._gateway.make_payment(cents, card)

Decorator
Attaches additional responsibilities to an object dynamically. Provides a flexible alternative to subclassing for extending
functionality. Decorators wrap the original object and add behavior before or after delegating to it. Python's @decorator
syntax is a language-level implementation of this pattern.

Page 3
import time, functools

def timed(func):
@[Link](func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
elapsed = time.perf_counter() - start
print(f'{func.__name__} took {elapsed:.3f}s')
return result
return wrapper

def retry(times=3):
def decorator(func):
@[Link](func)
def wrapper(*args, **kwargs):
for attempt in range(times):
try:
return func(*args, **kwargs)
except Exception as e:
if attempt == times - 1: raise
return wrapper
return decorator

@timed
@retry(times=3)
def fetch_data(url: str): ...

Facade
Provides a simplified interface to a complex subsystem. The facade hides the complexity and gives clients a clean entry
point. Common in layered architectures: a service class that orchestrates repositories, external APIs, and messaging is
a Facade.

class OrderFacade:
def __init__(self, inventory, payment, shipping, notifier):
self._inv = inventory
self._pay = payment
self._ship = shipping
self._notify = notifier

def place_order(self, user, cart, card):


# Client calls one method; facade orchestrates all subsystems
self._inv.reserve(cart)
receipt = self._pay.charge(card, [Link])
tracking = self._ship.dispatch([Link], cart)
self._notify.send([Link], tracking)
return receipt

Proxy
Provides a surrogate or placeholder for another object to control access. Types of proxy: Virtual (lazy initialization),
Remote (network call), Protection (access control), Caching (memoize expensive calls).

Page 4
class CachingProxy:
def __init__(self, real_service):
self._service = real_service
self._cache = {}

def get_user(self, user_id: int):


if user_id not in self._cache:
self._cache[user_id] = self._service.get_user(user_id)
return self._cache[user_id]

4. Behavioral Patterns

Observer
Defines a one-to-many dependency so that when one object (the subject) changes state, all its dependents (observers)
are notified automatically. Foundation of event-driven systems, reactive programming, and the publish-subscribe
pattern.

class EventBus:
def __init__(self):
self._listeners: dict[str, list] = {}

def subscribe(self, event: str, handler):


self._listeners.setdefault(event, []).append(handler)

def publish(self, event: str, data=None):


for handler in self._listeners.get(event, []):
handler(data)

bus = EventBus()
[Link]('[Link]', lambda d: print(f'Send email for {d}'))
[Link]('[Link]', lambda d: print(f'Update inventory for {d}'))
[Link]('[Link]', {'id': 42})

Strategy
Defines a family of algorithms, encapsulates each one, and makes them interchangeable. Lets the algorithm vary
independently from clients that use it. Replaces long if/elif chains that select different behaviors.

from typing import Protocol

class SortStrategy(Protocol):
def sort(self, data: list) -> list: ...

class QuickSort:
def sort(self, data): return sorted(data) # simplified

class BucketSort:
def sort(self, data): return sorted(data) # simplified

class DataProcessor:
def __init__(self, strategy: SortStrategy):
self._strategy = strategy

Page 5
def process(self, data: list) -> list:
return self._strategy.sort(data)

# Swap strategy at runtime


proc = DataProcessor(QuickSort())
proc._strategy = BucketSort()

Command
Encapsulates a request as an object, allowing parameterization, queuing, logging, and undo/redo. Each command
object knows how to execute AND how to reverse itself.

from dataclasses import dataclass


from typing import Protocol

class Command(Protocol):
def execute(self): ...
def undo(self): ...

@dataclass
class MoveFileCommand:
src: str; dst: str

def execute(self):
import shutil; [Link]([Link], [Link])

def undo(self):
import shutil; [Link]([Link], [Link])

class CommandHistory:
def __init__(self): self._history = []

def run(self, cmd: Command):


[Link]()
self._history.append(cmd)

def undo_last(self):
if self._history:
self._history.pop().undo()

Template Method
Defines the skeleton of an algorithm in a base class, deferring some steps to subclasses. Lets subclasses redefine
certain steps without changing the algorithm's structure. The base class calls abstract 'hook' methods that subclasses
implement.

class DataMigrator(ABC):
# Template method - defines the steps
def migrate(self):
data = [Link]()
transformed = [Link](data)
[Link](transformed)
self.notify_complete()

Page 6
@abstractmethod
def extract(self): ...

@abstractmethod
def transform(self, data): ...

@abstractmethod
def load(self, data): ...

def notify_complete(self): # optional hook


print('Migration done.')

5. Choosing the Right Pattern

Use this table as a quick decision guide when facing a recurring design problem:

Problem Pattern to consider

Need exactly one instance of a class Singleton


Creating objects without specifying exact class Factory Method / Abstract Factory
Constructing objects with many optional parameters Builder
Incompatible interfaces between classes Adapter
Add behavior to objects without subclassing Decorator
Simplify a complex subsystem API Facade
Control access or add caching to an object Proxy
Notify multiple objects when state changes Observer
Swap algorithms at runtime Strategy
Support undo/redo or queue requests Command
Share algorithm skeleton, vary steps in subclasses Template Method
Object changes behavior based on internal state State

6. Anti-Patterns: When Patterns Hurt

Patterns are tools, not goals. Applying them unnecessarily adds complexity. Common anti-patterns to avoid:

* Pattern fever: wrapping every operation in a Command when a plain function call suffices.
* Sin

7. Conclusion

Design patterns are a shared vocabulary for software architects. Knowing them helps you communicate solutions
clearly, recognize proven structures in existing codebases, and avoid reinventing wheels.

Page 7
The best engineers apply patterns judiciously: they reach for a pattern because it solves a real problem in the current
code, not because it sounds impressive or because 'we might need it later'.

Start by deeply understanding the most commonly used patterns: Factory, Strategy, Observer, Decorator, and Facade.
These appear in almost every non-trivial codebase and will give you the highest return on investment.

8. Additional Behavioral Patterns

State
Allows an object to alter its behavior when its internal state changes. The object will appear to change its class. Instead
of giant if/elif chains testing state, each state is its own class that implements the same interface.

class TrafficLight:
def __init__(self):
self._state = RedState()

def change(self):
self._state = self._state.next()

def signal(self):
return self._state.signal()

class RedState:
def signal(self): return 'STOP'
def next(self): return GreenState()

class GreenState:
def signal(self): return 'GO'
def next(self): return YellowState()

class YellowState:
def signal(self): return 'SLOW'
def next(self): return RedState()

Chain of Responsibility
Passes a request along a chain of handlers. Each handler decides to process the request or pass it to the next handler.
Used in middleware pipelines (HTTP middleware, logging chains, approval workflows). Decouples sender from
receivers.

class Handler:
def __init__(self, successor=None):
self._next = successor

def handle(self, request):


if self._next:
return self._next.handle(request)

class AuthHandler(Handler):
def handle(self, request):
if not [Link]('token'):

Page 8
return '401 Unauthorized'
return super().handle(request)

class RateLimitHandler(Handler):
def handle(self, request):
if [Link]('rate_exceeded'):
return '429 Too Many Requests'
return super().handle(request)

class BusinessHandler(Handler):
def handle(self, request):
return '200 OK - processed'

pipeline = AuthHandler(RateLimitHandler(BusinessHandler()))
print([Link]({'token': 'abc'}))

Mediator
Defines an object that encapsulates how a set of objects interact. Reduces chaotic dependencies between objects by
having them communicate only through the mediator. A chat room, an event bus, and an air traffic controller are all
examples of the mediator pattern.

Memento
Captures and externalizes an object's internal state without violating encapsulation, so the object can be restored to this
state later. The foundation of undo/redo in editors. The originator creates a memento snapshot; a caretaker stores it; the
originator can restore from a memento.

9. Patterns in Modern Frameworks

Design patterns appear throughout modern frameworks under different names:

Framework / Context Pattern in use

React useState / Redux Observer (re-render on state change)


React Context Singleton-like shared state
Express / Django middleware Chain of Responsibility
React higher-order components Decorator
ORM (SQLAlchemy, Hibernate) Active Record, Repository, Proxy
Dependency Injection containers Factory, Abstract Factory
Vue / MobX computed properties Lazy Proxy / Memoization
Event emitters ([Link]) Observer
Promise chaining (.then) Chain of Responsibility

10. Repository and Unit of Work Patterns

Repository
The Repository pattern abstracts the data layer. It provides a collection-like interface for accessing domain objects,
hiding database queries behind a clean API. Business logic talks to a repository; the repository talks to the database.

Page 9
This makes business logic testable without a real database.

from abc import ABC, abstractmethod

class UserRepository(ABC):
@abstractmethod
def find_by_id(self, user_id: int) -> User | None: ...
@abstractmethod
def find_by_email(self, email: str) -> User | None: ...
@abstractmethod
def save(self, user: User) -> User: ...
@abstractmethod
def delete(self, user_id: int) -> None: ...

class PostgresUserRepository(UserRepository):
def __init__(self, db): self._db = db
def find_by_id(self, user_id):
return self._db.query('SELECT * FROM users WHERE id=%s', user_id)
def save(self, user):
return self._db.execute('INSERT INTO users ...')

class InMemoryUserRepository(UserRepository): # for tests


def __init__(self): self._store = {}
def find_by_id(self, user_id): return self._store.get(user_id)
def save(self, user):
self._store[[Link]] = user; return user

Unit of Work
The Unit of Work pattern tracks all objects read from the database during a business transaction and writes all changes
in one atomic commit. It prevents partial updates and manages transaction boundaries cleanly.

SQLAlchemy's Session and JPA's EntityManager are both implementations of the Unit of Work pattern. The application
code loads objects, mutates them, and calls commit() - the UoW figures out what changed and issues the minimum set
of SQL statements.

11. Practical Pattern Recognition Exercise

One of the best ways to internalize patterns is to recognize them in real code. Here are common scenarios and the
patterns behind them:

* Python context managers (with open(...) as f) implement the Template Method pattern - setup, yield, teardown.
* Py

Page 10

You might also like