/python_design_patterns

Gang of Four design patterns in Python

Primary LanguagePython

Python Design Patterns

Description

  • This repository contains an overview of the Gang of Four (GoF) design patterns with modern-day variations, adjustments, and discussions in Python.

Learning Objectives

  • SOLID Design Principles:
  • Creational Design Patterns:
    • Builder
      • A builder is a separate component for building an object
      • Can either give builder an initializer or return it via a static function
      • To make builder fluent, return self
      • Different facets of an object can be built with different builders working in tandem via a base class
    • Factories (Factory Method and Abstract Factory)
      • A factory method is a static method that creates object
      • A factory is an entity that can take care of object creation
      • A factory can be external or reside inside the object as an inner class
      • Hierarchies of factories can be used to create related objects
    • Prototype
      • To implement a prototype, partially construct an object and store it somewhere
      • Deep copy the prototype
      • Customize the resulting instance
      • A factory provides a convenient API for using prototypes
    • Singleton
      • Different realizations of Singleton: custom allocator, decorator, metaclass
      • Laziness is easy, just init on first request
      • Monostate variation
      • Testability issues
  • Structural Design Patterns:
    • Adapter
      • Implementing an Adapter is easy
      • Determine the API you have and the API you need
      • Create a component which aggregates (has a reference to, ...) the adaptee
      • Intermediate representations can pile up: use caching and other optimizations
    • Bridge
      • Decouple abstraction from implementation
      • Both can exist as hierarchies
      • A stronger form of encapsulation
    • Composite
      • Objects can use other objects via inheritance/composition
      • Some composed and singular objects need similar/identical behaviors
      • Composite design pattern lets us treat both types of objects uniformly
      • Python supports iteration with __iter__ and Iterable ABC
      • A single object can itself iterable by yielding self from __iter__
    • Decorator
      • A decorator keeps the reference to the decorated object(s)
      • Adds utility attributes and methods to augment the object's features
      • May or may not forward calls to the underlying object
      • Proxying of underlying calls can be done dynamically
      • Python's functional decorators wrap functions; no direct relation to the GoF Decorator Pattern
    • Façade
      • Build a Facade to provide a simplified API over a set of classes
      • May wish to (optionally) expose internals through the facade
      • May allow users to 'escalate' to use more complex API
    • Flyweight
      • Store common data externally
      • Specify an index or a reference into the external data store
      • Define the idea of 'ranges' on homogeneuous collections and store data related to those ranges
    • Proxy
      • A proxy has the same interface as underlying object
      • To create a proxy, simple replicate the existing interface of an object
      • Add relevant functionality to the redefined member functions
      • Different proxies (communications, logging, caching, etc.) have completely different behaviors
  • Behavioral Design Patterns
    • Chain of Responsibility
      • Chain of responsibility can be implemented as a chain of references or a centralized construct
      • Enlist objects in the chain, possibly controlling their order
      • Object removal from chain (e.g., __exit__)
    • Command
      • Encapsulate all details of an operation in a separate object
      • Define instruction for applything the command (either in the command itself, or elsewhere)
      • Optionally define instructions for undoing the command
      • Can create composite commands (a.k.a. macros)
    • Interpreter
      • Barring simple cases, an interpreter acts in two stages
      • Lexing turns text into a set of tokens
      • Parsing tokens into meaningful construct
    • Iterator
      • An iterator specified how you can traverse an object
      • Stateful iterators cannot be recursive
      • yield allows for much more succinct iteration
    • Mediator
      • Create the mediator and have each object in the system refer to it
        • E.g., in a property
      • Mediator engages in bidirectional communication with its connected components
      • Mediator has functions the components can call
      • Components have functions the mediator can call
      • Event processing (e.g., Rx) libraries make communication easier to implement
    • Memento
      • Mementos are used to roll back states arbitrarily
      • A memento is simply a token/handle class with typically no function of its own
      • A memento is not required to expose directly the state(s) to which it reverts the system
      • Can be used to implement undo/redo
    • Observer
      • Observer is an intrusive approach: an observable must provide an event to subscribe to
      • Subsciption and unsubscription with additional/removal of items in list
      • Property notifications are easy: dependent property notifications are tricky
    • State
      • Given sufficient complexity, it pays to formally define possible states and events/triggers
      • Can define
        • State entry/exit behaviors
        • Action when a particular event causes a transition
        • Guard conditions enabling/disabling a transition
        • Default action when no transitions are found for an event
    • Strategy
      • Define an algorithm at a high level
      • Define the interface you expect each strategy to follow
      • Provide for dynamic composition of strategies in the resulting object
    • Template
      • Define an algorithm at a high level in parent class
      • Define constituent parts as abstract methods/properties
      • Inherit the algorthm class, providing the necessary ovverides
    • Visitor
      • OOP double-dispatch approach is not necessary in Python
      • Make a visitor, decorating each 'overload' with @visitor
      • Call visit() and the entire structure gets traversed

Usage

  • All files are created and executed on Mac OS X 10.11 with Python3 (version 3.7)

Awknowledgements

  • Dmitri Nesteruk's course "Design Patterns in Python" on Udemy.com

Author