본문으로 건너뛰기

디자인 패턴 기초

디자인 패턴(Design Pattern) 은 소프트웨어 설계에서 자주 발생하는 문제에 대한 재사용 가능한 해결책입니다. "바퀴를 다시 발명하지 말라"는 원칙처럼, 검증된 패턴을 활용하면 코드 품질을 높이고 팀원들과의 소통도 쉬워집니다.


Singleton 패턴

싱글톤(Singleton) 은 클래스의 인스턴스를 오직 하나만 생성하고, 어디서든 그 인스턴스에 접근할 수 있게 하는 패턴입니다. 설정 관리, 로거, DB 연결 풀 등에 사용됩니다.

방법 1: __new__ 오버라이딩

class Singleton:
_instance = None

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

def __init__(self, name: str = "default"):
# __init__은 매번 호출되므로 이미 초기화됐는지 확인
if not hasattr(self, "_initialized"):
self.name = name
self._initialized = True


s1 = Singleton("first")
s2 = Singleton("second")

print(s1 is s2) # True — 동일한 객체
print(s1.name) # first (s2는 새 인스턴스가 아님)
print(id(s1) == id(s2)) # True

방법 2: 모듈 레벨 (가장 Python스러운 방법)

# config.py 모듈로 분리하면 자연스러운 싱글톤
# Python 모듈은 처음 import 시 한 번만 실행되고 이후 캐싱됨

class AppConfig:
def __init__(self):
self.debug = False
self.db_url = "sqlite:///app.db"
self.secret_key = "change-me"
self.allowed_hosts: list[str] = ["localhost"]

def update(self, **kwargs) -> None:
for key, value in kwargs.items():
if hasattr(self, key):
setattr(self, key, value)
else:
raise KeyError(f"알 수 없는 설정: {key}")


# 모듈 레벨에서 인스턴스 생성 — 사실상 싱글톤
config = AppConfig()

# 사용처에서는 import만 하면 됨
# from config import config
# config.debug = True

방법 3: Metaclass 활용

class SingletonMeta(type):
"""싱글톤 동작을 부여하는 메타클래스"""
_instances: dict = {}

def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]


class DatabasePool(metaclass=SingletonMeta):
"""DB 연결 풀 — 애플리케이션에 하나만 존재"""
def __init__(self, max_connections: int = 10):
self.max_connections = max_connections
self._connections: list = []
print(f"DB 연결 풀 초기화 (최대 {max_connections}개)")

def get_connection(self):
if len(self._connections) < self.max_connections:
conn = f"conn_{len(self._connections) + 1}"
self._connections.append(conn)
return conn
raise RuntimeError("연결 풀이 가득 찼습니다.")

def release(self, conn) -> None:
self._connections.remove(conn)

def status(self) -> str:
return f"사용 중: {len(self._connections)}/{self.max_connections}"


class Logger(metaclass=SingletonMeta):
"""애플리케이션 전역 로거 — 싱글톤"""
def __init__(self):
self._logs: list[str] = []

def info(self, msg: str) -> None:
import datetime
entry = f"[INFO {datetime.datetime.now():%H:%M:%S}] {msg}"
self._logs.append(entry)
print(entry)

def warning(self, msg: str) -> None:
import datetime
entry = f"[WARN {datetime.datetime.now():%H:%M:%S}] {msg}"
self._logs.append(entry)
print(entry)

def get_all(self) -> list[str]:
return list(self._logs)


# 싱글톤 동작 확인
pool1 = DatabasePool(5)
pool2 = DatabasePool(100) # 이미 생성된 인스턴스 반환, 인자 무시

print(pool1 is pool2) # True
print(pool1.max_connections) # 5 (pool2로 바뀌지 않음)

logger1 = Logger()
logger2 = Logger()
logger1.info("서버 시작")
logger2.warning("메모리 부족") # logger1과 같은 객체

print(logger1 is logger2) # True
print(len(logger1.get_all())) # 2 (두 개 다 같은 로거에 기록됨)

Factory 패턴

팩토리(Factory) 패턴 은 객체 생성 로직을 분리하여 클라이언트 코드가 구체적인 클래스를 몰라도 되게 합니다.

Simple Factory

from abc import ABC, abstractmethod


class Animal(ABC):
def __init__(self, name: str):
self.name = name

@abstractmethod
def speak(self) -> str: ...

def __repr__(self) -> str:
return f"{self.__class__.__name__}({self.name!r})"


class Dog(Animal):
def speak(self) -> str:
return f"{self.name}: 왈왈!"


class Cat(Animal):
def speak(self) -> str:
return f"{self.name}: 야옹~"


class Bird(Animal):
def speak(self) -> str:
return f"{self.name}: 짹짹!"


class AnimalFactory:
"""Simple Factory: 정적 메서드로 객체 생성"""

_creators = {
"dog": Dog,
"cat": Cat,
"bird": Bird,
}

@classmethod
def create(cls, animal_type: str, name: str) -> Animal:
creator = cls._creators.get(animal_type.lower())
if creator is None:
available = ", ".join(cls._creators)
raise ValueError(f"알 수 없는 동물: {animal_type!r}. 가능한 타입: {available}")
return creator(name)

@classmethod
def register(cls, animal_type: str, creator: type) -> None:
"""새 동물 타입 등록 — OCP 적용"""
cls._creators[animal_type.lower()] = creator


# 사용
animals = [
AnimalFactory.create("dog", "바둑이"),
AnimalFactory.create("cat", "나비"),
AnimalFactory.create("bird", "짹짹이"),
]

for animal in animals:
print(animal.speak())

# 새 동물 추가 — AnimalFactory 코드 수정 없음
class Rabbit(Animal):
def speak(self) -> str:
return f"{self.name}: 토토~"

AnimalFactory.register("rabbit", Rabbit)
bunny = AnimalFactory.create("rabbit", "뭉치")
print(bunny.speak())

Factory Method 패턴

from abc import ABC, abstractmethod


class Button(ABC):
@abstractmethod
def render(self) -> str: ...

@abstractmethod
def on_click(self) -> str: ...


class WindowsButton(Button):
def render(self) -> str:
return "[ Windows Button ]"

def on_click(self) -> str:
return "Windows 클릭 이벤트 발생"


class MacButton(Button):
def render(self) -> str:
return "( Mac Button )"

def on_click(self) -> str:
return "Mac 클릭 이벤트 발생"


class WebButton(Button):
def render(self) -> str:
return "<button>Web Button</button>"

def on_click(self) -> str:
return "DOM 클릭 이벤트 발생"


class Dialog(ABC):
"""Factory Method를 정의하는 추상 클래스"""

@abstractmethod
def create_button(self) -> Button:
"""팩토리 메서드 — 서브클래스에서 구현"""
...

def render_dialog(self) -> str:
"""팩토리 메서드로 생성한 버튼을 사용"""
button = self.create_button()
return f"Dialog: {button.render()}"

def handle_click(self) -> str:
button = self.create_button()
return button.on_click()


class WindowsDialog(Dialog):
def create_button(self) -> Button:
return WindowsButton()


class MacDialog(Dialog):
def create_button(self) -> Button:
return MacButton()


class WebDialog(Dialog):
def create_button(self) -> Button:
return WebButton()


import platform

def get_dialog() -> Dialog:
"""환경에 따라 적절한 Dialog 반환"""
system = platform.system()
if system == "Windows":
return WindowsDialog()
elif system == "Darwin":
return MacDialog()
else:
return WebDialog()


dialog = get_dialog()
print(dialog.render_dialog())
print(dialog.handle_click())

Abstract Factory 패턴

from abc import ABC, abstractmethod


# 추상 제품들
class TextInput(ABC):
@abstractmethod
def render(self) -> str: ...


class Checkbox(ABC):
@abstractmethod
def render(self) -> str: ...


class Button(ABC):
@abstractmethod
def render(self) -> str: ...


# 구체 제품들 (Windows)
class WindowsTextInput(TextInput):
def render(self) -> str:
return "[___ Windows TextInput ___]"


class WindowsCheckbox(Checkbox):
def render(self) -> str:
return "[x] Windows Checkbox"


class WindowsButton(Button):
def render(self) -> str:
return "[ OK ]"


# 구체 제품들 (Mac)
class MacTextInput(TextInput):
def render(self) -> str:
return "( Mac TextInput )"


class MacCheckbox(Checkbox):
def render(self) -> str:
return "(✓) Mac Checkbox"


class MacButton(Button):
def render(self) -> str:
return "( OK )"


# 추상 팩토리
class UIFactory(ABC):
@abstractmethod
def create_text_input(self) -> TextInput: ...

@abstractmethod
def create_checkbox(self) -> Checkbox: ...

@abstractmethod
def create_button(self) -> Button: ...


# 구체 팩토리들
class WindowsUIFactory(UIFactory):
def create_text_input(self) -> TextInput:
return WindowsTextInput()

def create_checkbox(self) -> Checkbox:
return WindowsCheckbox()

def create_button(self) -> Button:
return WindowsButton()


class MacUIFactory(UIFactory):
def create_text_input(self) -> TextInput:
return MacTextInput()

def create_checkbox(self) -> Checkbox:
return MacCheckbox()

def create_button(self) -> Button:
return MacButton()


class LoginForm:
"""팩토리로 생성된 UI 컴포넌트를 사용 — 구체 클래스 모름"""
def __init__(self, factory: UIFactory):
self.username_input = factory.create_text_input()
self.remember_me = factory.create_checkbox()
self.submit_btn = factory.create_button()

def render(self) -> str:
return (f"=== 로그인 ===\n"
f" {self.username_input.render()}\n"
f" {self.remember_me.render()}\n"
f" {self.submit_btn.render()}")


for factory in [WindowsUIFactory(), MacUIFactory()]:
form = LoginForm(factory)
print(form.render())
print()

Observer 패턴

옵저버(Observer) 패턴 은 객체의 상태 변화를 다른 객체들에게 자동으로 알리는 패턴입니다. 이벤트 시스템, GUI 업데이트, 알림 기능 등에 활용됩니다.

기본 구현

from abc import ABC, abstractmethod
from typing import Any


class Observer(ABC):
@abstractmethod
def update(self, event: str, data: Any) -> None: ...


class Subject:
"""관찰 대상 (Publisher)"""

def __init__(self):
self._observers: dict[str, list[Observer]] = {}

def subscribe(self, event: str, observer: Observer) -> None:
self._observers.setdefault(event, []).append(observer)

def unsubscribe(self, event: str, observer: Observer) -> None:
if event in self._observers:
self._observers[event].remove(observer)

def notify(self, event: str, data: Any = None) -> None:
for observer in self._observers.get(event, []):
observer.update(event, data)


class StockMarket(Subject):
"""주식 시장 — 가격 변동을 옵저버에게 알림"""

def __init__(self):
super().__init__()
self._prices: dict[str, float] = {}

def update_price(self, symbol: str, price: float) -> None:
old_price = self._prices.get(symbol, 0)
self._prices[symbol] = price
change = price - old_price
change_pct = (change / old_price * 100) if old_price else 0
self.notify("price_changed", {
"symbol": symbol,
"price": price,
"change": change,
"change_pct": change_pct,
})

def get_price(self, symbol: str) -> float:
return self._prices.get(symbol, 0)


class AlertObserver(Observer):
"""가격 변동 알림"""
def __init__(self, threshold_pct: float = 5.0):
self.threshold = threshold_pct

def update(self, event: str, data: dict) -> None:
if abs(data["change_pct"]) >= self.threshold:
direction = "급등" if data["change"] > 0 else "급락"
print(f"[알림] {data['symbol']} {direction}! "
f"{data['price']:,.0f}원 ({data['change_pct']:+.1f}%)")


class PortfolioObserver(Observer):
"""포트폴리오 손익 추적"""
def __init__(self, name: str):
self.name = name
self._holdings: dict[str, tuple[int, float]] = {} # {symbol: (수량, 매수가)}
self._pnl = 0.0

def add_holding(self, symbol: str, quantity: int, buy_price: float) -> None:
self._holdings[symbol] = (quantity, buy_price)

def update(self, event: str, data: dict) -> None:
symbol = data["symbol"]
if symbol in self._holdings:
qty, buy_price = self._holdings[symbol]
pnl = (data["price"] - buy_price) * qty
pnl_pct = (data["price"] - buy_price) / buy_price * 100
print(f"[{self.name}] {symbol}: {pnl:+,.0f}원 ({pnl_pct:+.1f}%)")


class LogObserver(Observer):
"""모든 가격 변동 기록"""
def __init__(self):
self._log: list[str] = []

def update(self, event: str, data: dict) -> None:
entry = (f"{data['symbol']}: {data['price']:,.0f}원 "
f"({data['change_pct']:+.1f}%)")
self._log.append(entry)

def get_log(self) -> list[str]:
return list(self._log)


# 사용 예시
market = StockMarket()

alert = AlertObserver(threshold_pct=3.0)
portfolio = PortfolioObserver("김철수")
logger = LogObserver()

portfolio.add_holding("SAMSUNG", 10, 70_000)
portfolio.add_holding("KAKAO", 5, 50_000)

market.subscribe("price_changed", alert)
market.subscribe("price_changed", portfolio)
market.subscribe("price_changed", logger)

# 가격 업데이트
market.update_price("SAMSUNG", 75_000)
market.update_price("KAKAO", 45_000)
market.update_price("NAVER", 200_000)

print("\n전체 로그:")
for entry in logger.get_log():
print(f" {entry}")

weakref를 활용한 메모리 안전 Observer

import weakref
from typing import Callable, Any


class EventEmitter:
"""weakref를 사용하여 옵저버 메모리 누수를 방지하는 이벤트 에미터"""

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

def on(self, event: str, callback: Callable) -> None:
"""이벤트 리스너 등록 (weakref 사용)"""
if event not in self._listeners:
self._listeners[event] = []
# 메서드는 weakref.WeakMethod, 함수는 weakref.ref 사용
try:
ref = weakref.WeakMethod(callback)
except TypeError:
ref = weakref.ref(callback)
self._listeners[event].append(ref)

def emit(self, event: str, *args: Any, **kwargs: Any) -> None:
"""이벤트 발생 — 죽은 weakref는 자동 제거"""
listeners = self._listeners.get(event, [])
alive = []
for ref in listeners:
callback = ref()
if callback is not None:
callback(*args, **kwargs)
alive.append(ref)
self._listeners[event] = alive


class Button(EventEmitter):
def __init__(self, label: str):
super().__init__()
self.label = label

def click(self) -> None:
print(f"[버튼 '{self.label}' 클릭]")
self.emit("click", self)


class ClickCounter:
def __init__(self, name: str):
self.name = name
self.count = 0

def on_click(self, button: Button) -> None:
self.count += 1
print(f"[{self.name}] '{button.label}' 클릭 횟수: {self.count}")


btn = Button("확인")
counter = ClickCounter("카운터")

btn.on("click", counter.on_click)
btn.click()
btn.click()

# counter 삭제 후 — weakref이므로 자동 정리
del counter
btn.click() # 아무 일도 없음 (죽은 weakref 자동 제거)

Strategy 패턴

전략(Strategy) 패턴 은 알고리즘 군을 정의하고 각각을 캡슐화하여 교환 가능하게 만드는 패턴입니다. Python은 함수가 일급 객체이므로 더 간결하게 구현할 수 있습니다.

클래스 기반 Strategy

from abc import ABC, abstractmethod


class SortStrategy(ABC):
@abstractmethod
def sort(self, data: list) -> list: ...

@abstractmethod
def name(self) -> str: ...


class BubbleSort(SortStrategy):
def sort(self, data: list) -> list:
arr = list(data)
n = len(arr)
for i in range(n):
for j in range(n - i - 1):
if arr[j] > arr[j + 1]:
arr[j], arr[j + 1] = arr[j + 1], arr[j]
return arr

def name(self) -> str:
return "버블 정렬"


class QuickSort(SortStrategy):
def sort(self, data: list) -> list:
if len(data) <= 1:
return list(data)
pivot = data[len(data) // 2]
left = [x for x in data if x < pivot]
middle = [x for x in data if x == pivot]
right = [x for x in data if x > pivot]
return self.sort(left) + middle + self.sort(right)

def name(self) -> str:
return "퀵 정렬"


class MergeSort(SortStrategy):
def sort(self, data: list) -> list:
if len(data) <= 1:
return list(data)
mid = len(data) // 2
left = self.sort(data[:mid])
right = self.sort(data[mid:])
return self._merge(left, right)

def _merge(self, left: list, right: list) -> list:
result = []
i = j = 0
while i < len(left) and j < len(right):
if left[i] <= right[j]:
result.append(left[i])
i += 1
else:
result.append(right[j])
j += 1
result.extend(left[i:])
result.extend(right[j:])
return result

def name(self) -> str:
return "병합 정렬"


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

def set_strategy(self, strategy: SortStrategy) -> None:
"""런타임에 전략 변경 가능"""
self._strategy = strategy

def sort(self, data: list) -> list:
print(f"[{self._strategy.name()}] 정렬 시작...")
result = self._strategy.sort(data)
return result


data = [64, 34, 25, 12, 22, 11, 90]
sorter = Sorter(BubbleSort())

print(sorter.sort(data))

sorter.set_strategy(QuickSort())
print(sorter.sort(data))

함수를 전략으로 — Python 스타일

from typing import Callable


# Python에서는 함수 자체를 전략으로 사용할 수 있음
SortFunc = Callable[[list], list]


class FlexibleSorter:
def __init__(self, strategy: SortFunc | None = None):
self._strategy = strategy or sorted

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


def reverse_sort(data: list) -> list:
return sorted(data, reverse=True)


def sort_by_length(data: list) -> list:
return sorted(data, key=lambda x: len(str(x)))


# 함수를 전략으로 주입
sorter1 = FlexibleSorter() # 기본 정렬
sorter2 = FlexibleSorter(reverse_sort) # 역순 정렬
sorter3 = FlexibleSorter(sort_by_length) # 길이 기준 정렬
sorter4 = FlexibleSorter(lambda d: sorted(d, key=lambda x: -x)) # 람다도 가능

nums = [42, 7, 100, 3, 55, 18]
print(sorter1.sort(nums)) # [3, 7, 18, 42, 55, 100]
print(sorter2.sort(nums)) # [100, 55, 42, 18, 7, 3]
print(sorter3.sort(nums)) # [7, 3, 42, 18, 55, 100]


# 실전: 결제 처리 전략
class PaymentProcessor:
def __init__(self, strategy: Callable[[float], str]):
self._strategy = strategy

def process(self, amount: float) -> str:
return self._strategy(amount)


def pay_with_card(amount: float) -> str:
return f"카드 결제: {amount:,.0f}원"


def pay_with_kakao(amount: float) -> str:
return f"카카오페이 결제: {amount:,.0f}원"


def pay_with_crypto(amount: float) -> str:
return f"암호화폐 결제: {amount:,.0f}원 (변동 환율 적용)"


processor = PaymentProcessor(pay_with_card)
print(processor.process(35_000))

processor._strategy = pay_with_kakao
print(processor.process(35_000))

Decorator 패턴

데코레이터(Decorator) 패턴 은 객체에 동적으로 새로운 기능을 추가하는 패턴입니다. 주의: Python의 @decorator 문법과는 다른 개념입니다 (동일한 이름이지만 GoF 패턴과 Python 언어 기능은 별개입니다).

GoF Decorator 패턴

from abc import ABC, abstractmethod


class Coffee(ABC):
"""음료 기반 인터페이스"""

@abstractmethod
def cost(self) -> float: ...

@abstractmethod
def description(self) -> str: ...


class Espresso(Coffee):
def cost(self) -> float:
return 3_000

def description(self) -> str:
return "에스프레소"


class Americano(Coffee):
def cost(self) -> float:
return 4_000

def description(self) -> str:
return "아메리카노"


class CoffeeDecorator(Coffee, ABC):
"""데코레이터 기반 클래스"""

def __init__(self, coffee: Coffee):
self._coffee = coffee

def cost(self) -> float:
return self._coffee.cost()

def description(self) -> str:
return self._coffee.description()


class MilkDecorator(CoffeeDecorator):
def cost(self) -> float:
return super().cost() + 500

def description(self) -> str:
return f"{super().description()} + 우유"


class SyrupDecorator(CoffeeDecorator):
def __init__(self, coffee: Coffee, syrup_type: str = "바닐라"):
super().__init__(coffee)
self.syrup_type = syrup_type

def cost(self) -> float:
return super().cost() + 700

def description(self) -> str:
return f"{super().description()} + {self.syrup_type} 시럽"


class WhipDecorator(CoffeeDecorator):
def cost(self) -> float:
return super().cost() + 1_000

def description(self) -> str:
return f"{super().description()} + 휘핑크림"


# 주문 조합
order1 = Espresso()
order2 = MilkDecorator(Americano())
order3 = WhipDecorator(SyrupDecorator(MilkDecorator(Americano()), "카라멜"))

for order in [order1, order2, order3]:
print(f"{order.description()}: {order.cost():,}원")

Python 데코레이터로 GoF Decorator 구현

import functools
import time
from typing import Callable


# Python의 @decorator는 함수에 기능을 추가하는 구문 설탕
def timer(func: Callable) -> Callable:
"""함수 실행 시간을 측정하는 데코레이터"""
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
elapsed = time.perf_counter() - start
print(f"[타이머] {func.__name__}: {elapsed:.4f}초")
return result
return wrapper


def retry(max_attempts: int = 3, exceptions: tuple = (Exception,)):
"""실패 시 재시도하는 데코레이터"""
def decorator(func: Callable) -> Callable:
@functools.wraps(func)
def wrapper(*args, **kwargs):
for attempt in range(1, max_attempts + 1):
try:
return func(*args, **kwargs)
except exceptions as e:
if attempt == max_attempts:
raise
print(f"[재시도] {func.__name__} 실패 ({attempt}/{max_attempts}): {e}")
return wrapper
return decorator


def cache(func: Callable) -> Callable:
"""결과를 캐싱하는 데코레이터"""
_cache: dict = {}

@functools.wraps(func)
def wrapper(*args):
if args not in _cache:
_cache[args] = func(*args)
return _cache[args]

wrapper.cache_clear = lambda: _cache.clear()
wrapper.cache_info = lambda: {"size": len(_cache), "keys": list(_cache.keys())}
return wrapper


def validate_types(**type_hints):
"""인수 타입을 검증하는 데코레이터"""
def decorator(func: Callable) -> Callable:
@functools.wraps(func)
def wrapper(*args, **kwargs):
import inspect
sig = inspect.signature(func)
bound = sig.bind(*args, **kwargs)
for param_name, expected_type in type_hints.items():
if param_name in bound.arguments:
value = bound.arguments[param_name]
if not isinstance(value, expected_type):
raise TypeError(
f"{param_name}{expected_type.__name__}이어야 합니다, "
f"받은 타입: {type(value).__name__}"
)
return func(*args, **kwargs)
return wrapper
return decorator


# 여러 데코레이터 조합 (데코레이터도 조합 가능 — GoF Decorator처럼)
@timer
@cache
def fibonacci(n: int) -> int:
if n <= 1:
return n
return fibonacci(n - 1) + fibonacci(n - 2)


@validate_types(name=str, age=int)
def greet(name: str, age: int) -> str:
return f"안녕하세요, {name}님! ({age}세)"


@retry(max_attempts=3, exceptions=(ValueError,))
def unstable_operation(value: int) -> int:
import random
if random.random() < 0.5:
raise ValueError("임시 오류 발생!")
return value * 2


print(fibonacci(30))
print(fibonacci(30)) # 캐시에서 즉시 반환

print(greet("김철수", 25))
try:
greet("이름", "나이") # TypeError 발생
except TypeError as e:
print(f"타입 오류: {e}")

고수 팁

1. 패턴의 선택 기준

# 패턴은 문제가 있을 때만 사용하라
# 단순한 경우: 패턴 불필요
def process(data):
return sorted(data)

# 복잡한 경우 (알고리즘 교체 필요): Strategy 패턴
class DataProcessor:
def __init__(self, sort_strategy=sorted):
self.sort = sort_strategy

def process(self, data):
return self.sort(data)

2. Singleton의 대안: 모듈 수준 변수

# Singleton 클래스보다 모듈 수준 변수가 더 간단하고 Python스럽다
# settings.py
DEBUG = False
DATABASE_URL = "sqlite:///app.db"
MAX_CONNECTIONS = 10

# 사용처
# import settings
# if settings.DEBUG: ...

3. __init_subclass__로 Factory 자동 등록

class Plugin:
_registry: dict[str, type] = {}

def __init_subclass__(cls, plugin_name: str = "", **kwargs):
super().__init_subclass__(**kwargs)
if plugin_name:
Plugin._registry[plugin_name] = cls
print(f"플러그인 등록: {plugin_name}{cls.__name__}")

@classmethod
def create(cls, name: str) -> "Plugin":
if name not in cls._registry:
raise KeyError(f"등록되지 않은 플러그인: {name}")
return cls._registry[name]()

def run(self) -> str:
raise NotImplementedError


class AudioPlugin(Plugin, plugin_name="audio"):
def run(self) -> str:
return "오디오 처리 중..."


class VideoPlugin(Plugin, plugin_name="video"):
def run(self) -> str:
return "비디오 처리 중..."


for name in ["audio", "video"]:
plugin = Plugin.create(name)
print(plugin.run())

정리

패턴목적Python 특징
Singleton인스턴스 하나만모듈 레벨 변수가 더 Python스럽다
Factory생성 로직 분리__init_subclass__로 자동 등록
Observer이벤트 알림weakref로 메모리 누수 방지
Strategy알고리즘 교체함수가 일급 객체이므로 더 간결
Decorator기능 추가@decorator 문법으로 우아하게

디자인 패턴은 수단이지 목적이 아닙니다. "이 패턴을 쓰고 싶다"가 아니라 "이 문제를 해결하는 데 이 패턴이 적합하다"는 관점으로 접근하세요.