Saltar a contenido

Week 17 - Pythonic Design Patterns

17.1 Conceptual Core

The GoF book describes patches around C++ and Java limitations: no first-class functions, no closures, no duck typing, mandatory class hierarchies. Many "patterns" in Python collapse to language features. Some still apply. Knowing which is which is senior-level taste.

17.2 The Catalog, Translated

GoF Pattern Pythonic Form
Strategy A function passed as an argument. Or a Protocol.
Template Method A function with hooks; ABC + abstractmethod only when you need enforcement.
Factory / Abstract Factory A function. Or __init_subclass__ registry. Or functools.singledispatch.
Singleton A module. import is the singleton. @lru_cache(maxsize=None) on a constructor for parameterized singletons.
Observer signal/blinker, asyncio Queue, or just a list of callbacks.
Iterator Built into the language (__iter__).
Decorator (GoF) Decorators (Python).
Adapter A function. Or Protocol + a thin wrapper class.
Visitor functools.singledispatch for type dispatch; match statement for ADTs.
Command A callable. functools.partial for binding.
Chain of Responsibility Middleware. ASGI/WSGI middleware is exactly this.
State Functions returning functions; or a match over an Enum.
Builder Keyword args + dataclass. Rarely a builder class.
Flyweight sys.intern for strings; __slots__ + class-level constants.
Proxy __getattr__-based forwarding; or weakref.proxy.
Composite A type that contains itself: tree: Node | list[Node].
Memento dataclasses.replace + immutability.
Mediator An event bus or a domain service.

The patterns that survive idiomatically: Strategy (via Protocol), Observer (via async queues), Chain (via middleware), Visitor (via singledispatch / match).

17.3 Architectural Patterns

  • Hexagonal / Ports and Adapters. Domain at the core, adapters at the edge (HTTP, DB, message bus). Test the domain in isolation. Architecture Patterns with Python (Percival/Gregory) is the canonical Python treatment.
  • Repository pattern: abstract persistence behind a Protocol. Tests use an in-memory fake; production uses SQL.
  • Unit of Work: collect domain mutations, commit atomically. Pairs with SQLAlchemy session.
  • CQRS-lite: separate read models from write models when their shapes diverge. Don't over-apply.

17.4 Lab - "Refactor a Junk Drawer"

  1. Take a 1k-LOC script of mixed responsibilities. Extract: domain/, adapters/, service/, entrypoints/. Write Protocols for the seams.
  2. Add a fake repository for tests; the real one talks to SQLite. Run the same test suite against both.
  3. Document, in a docs/architecture.md, why each module exists and what it depends on.

17.5 Idiomatic & Linter Drill

  • import-linter to enforce the dependency direction (entrypoints → service → domain ← adapters).

Comments