Python Design Patterns in Practice: Implementing Strategy, Observer, and Factory Patterns the Pythonic Way

Updated Feb 6, 2026

Design Patterns: How to Use Them in Python?

Design patterns are proven design methods for solving problems that repeatedly appear in software development. Thanks to Python’s powerful language features such as dynamic typing, first-class functions, and decorators, traditional design patterns can be implemented in a more concise and elegant way.

In this article, we’ll explore how to implement the most commonly used Strategy, Observer, and Factory patterns in Python style, along with practical use cases.

Key Point: In Python, classes are not the only way to implement patterns. Using functions, dictionaries, and decorators allows you to write more Pythonic code.


Strategy Pattern: Dynamically Swapping Algorithms

Concept

The Strategy pattern encapsulates algorithms to allow dynamic replacement at runtime. It’s useful when you need to perform the same task in multiple ways.

Traditional Implementation vs Pythonic Implementation

Approach Characteristics Code Volume
Class-based Explicit interface, type safety High
Function-based Concise, leverages first-class functions Low
Dictionary-based Dynamic registration, easy extension Medium

Real-world Example: Discount Policy System

from typing import Callable

# Pythonic 방식: 함수를 전략으로 사용
def no_discount(price: float) -> float:
    return price

def percentage_discount(percent: float) -> Callable[[float], float]:
    return lambda price: price * (1 - percent / 100)

def fixed_discount(amount: float) -> Callable[[float], float]:
    return lambda price: max(0, price - amount)

class Order:
    def __init__(self, price: float, discount_strategy: Callable[[float], float] = no_discount):
        self.price = price
        self.discount_strategy = discount_strategy

    def final_price(self) -> float:
        return self.discount_strategy(self.price)

# 사용 예시
order1 = Order(10000, percentage_discount(20))  # 20% 할인
print(order1.final_price())  # 8000.0

order2 = Order(10000, fixed_discount(3000))  # 3000원 할인
print(order2.final_price())  # 7000.0

Advantages:
– Eliminates if-elif chains
– No need to modify existing code when adding new discount policies (OCP principle)
– Easy to test (each strategy can be tested independently)


Observer Pattern: Event-based Communication

Concept

The Observer pattern automatically notifies multiple observers of state changes in a subject. It’s essential for GUI, event systems, and real-time data processing.

Pythonic Implementation: Decorator + weakref

from weakref import WeakSet
from typing import Callable, Any

class Event:
    def __init__(self):
        self._observers = WeakSet()  # 메모리 누수 방지

    def subscribe(self, observer: Callable) -> None:
        self._observers.add(observer)

    def unsubscribe(self, observer: Callable) -> None:
        self._observers.discard(observer)

    def notify(self, *args, **kwargs) -> None:
        for observer in self._observers:
            observer(*args, **kwargs)

class Stock:
    def __init__(self, symbol: str, price: float):
        self.symbol = symbol
        self._price = price
        self.price_changed = Event()

    @property
    def price(self) -> float:
        return self._price

    @price.setter
    def price(self, value: float) -> None:
        if value != self._price:
            old_price = self._price
            self._price = value
            self.price_changed.notify(self.symbol, old_price, value)

# 사용 예시
def alert_high_price(symbol: str, old: float, new: float) -> None:
    if new > 100000:
        print(f"⚠️ {symbol} 고가 경보: {old:,}{new:,}원")

def log_price_change(symbol: str, old: float, new: float) -> None:
    print(f"📊 {symbol} 가격 변동: {old:,}{new:,}원 ({new-old:+,}원)")

stock = Stock("AAPL", 95000)
stock.price_changed.subscribe(alert_high_price)
stock.price_changed.subscribe(log_price_change)

stock.price = 105000
# 출력:
# 📊 AAPL 가격 변동: 95,000 → 105,000원 (+10,000원)
# ⚠️ AAPL 고가 경보: 95,000 → 105,000원

Real-world Use Cases:
Web Crawler: Execute multiple post-processing tasks (DB storage, notification sending, logging) when data collection completes
ML Training: Save checkpoints, perform validation, update TensorBoard when epoch completes
IoT: Simultaneously propagate sensor value changes to multiple controllers


Factory Pattern: Encapsulating Object Creation

Concept

The Factory pattern separates object creation logic to increase code flexibility. Use it when the class to create is determined at runtime or when complex initialization is needed.

Pythonic Implementation: Dictionary + Type Hints

from abc import ABC, abstractmethod
from typing import Dict, Type

class DataParser(ABC):
    @abstractmethod
    def parse(self, data: str) -> dict:
        pass

class JSONParser(DataParser):
    def parse(self, data: str) -> dict:
        import json
        return json.loads(data)

class XMLParser(DataParser):
    def parse(self, data: str) -> dict:
        import xmltodict
        return xmltodict.parse(data)

class YAMLParser(DataParser):
    def parse(self, data: str) -> dict:
        import yaml
        return yaml.safe_load(data)

# Factory
class ParserFactory:
    _parsers: Dict[str, Type[DataParser]] = {}

    @classmethod
    def register(cls, format_name: str, parser_class: Type[DataParser]) -> None:
        cls._parsers[format_name] = parser_class

    @classmethod
    def create(cls, format_name: str) -> DataParser:
        parser_class = cls._parsers.get(format_name)
        if not parser_class:
            raise ValueError(f"지원하지 않는 형식: {format_name}")
        return parser_class()

# 등록
ParserFactory.register('json', JSONParser)
ParserFactory.register('xml', XMLParser)
ParserFactory.register('yaml', YAMLParser)

# 사용 예시
def process_data(data: str, format: str) -> dict:
    parser = ParserFactory.create(format)
    return parser.parse(data)

result = process_data('{"key": "value"}', 'json')
print(result)  # {'key': 'value'}

Automatic Registration Using Decorators

def register_parser(format_name: str):
    def decorator(cls: Type[DataParser]):
        ParserFactory.register(format_name, cls)
        return cls
    return decorator

@register_parser('csv')
class CSVParser(DataParser):
    def parse(self, data: str) -> dict:
        import csv
        import io
        reader = csv.DictReader(io.StringIO(data))
        return list(reader)

Advantages:
– Automatically registered when adding new parsers by simply attaching decorator
– No need to modify factory code (OCP principle)


Pattern Comparison and Selection Guide

Pattern Problem Solved When to Use Python-specific Techniques
Strategy Algorithm swapping Same task, various methods First-class functions, lambda
Observer Event propagation 1:N dependency relationships WeakSet, properties
Factory Separate object creation Runtime type determination Dictionary, decorators

Real-world Combination Example: Data Processing Pipeline

# Strategy: 다양한 전처리 방법
preprocessors = {
    'normalize': lambda x: (x - x.mean()) / x.std(),
    'minmax': lambda x: (x - x.min()) / (x.max() - x.min()),
}

# Factory: 데이터 로더 생성
loader = DataLoaderFactory.create('csv')

# Observer: 처리 진행률 모니터링
pipeline.progress_updated.subscribe(lambda p: print(f"진행률: {p}%"))

Conclusion

When applying design patterns in Python, the key is to maximize the language’s characteristics.

Key Summary:
1. Strategy Pattern: Implement concisely without classes by leveraging first-class functions
2. Observer Pattern: Prevent memory leaks with WeakSet, automatic notification with properties
3. Factory Pattern: Build dynamic registration system with dictionary + decorators

Practical Tip: Rather than applying patterns unconditionally, introduce them when you see code smells such as “repetitive code”, “if-elif chains”, or “tight coupling”.

By reinterpreting traditional design patterns in a Pythonic way, you can write code that’s more readable and maintainable. Try using these patterns in your next project!

Did you find this helpful?

☕ Buy me a coffee

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

TODAY 396 | TOTAL 2,619