디자인 패턴이 언어에 독립적이긴 하지만, 모든 언어에 적용될 수 있는 것은 아니고, '동적 언어에서의 디자인 패턴 발표'에서 피터 노빅은 에릭 감마와 공저한 디자인 패턴에서 정의한 23개의 패턴 중 16개는 동적 언어에서 '보이지 않거나 더 단순하다'고 설명한다.
전략 패턴은 파이썬에서 함수를 일급 객체로 사용하면 더욱 간단해질 수 있는 디자인 패턴의 대표적인 사례로 고전적인 구조를 이용해서 전략 패턴을 설명하고 구현한다. 고전적인 패턴에 익숙하다면, 함수를 이용해서 코드 리팩토링하고 소스 코드를 엄청나게 줄일 수 있다.
콘텍스트
일부 계산을 서로 다른 알고리즘을 구현하는 교환 가능한 컴포넌트에 위임함으로써 서비스를 제공하여서 전자상거래 예제에는 콘텍스트는 Order로서, 여러 알고리즘 중 하나에 따라 프로모션 할인을 적용하도록 설정된다.
전략
여러 알고리즘을 구현하는 컴포넌트에 공통된 인터페이스, 전자상거래 예제에서는 이 역할을 Promotion이라는 추상 클래스가 담당한다.
구체적인 전략
전략의 구상 서브클래스 중 하나, 여기서는 FidelityPromo, BulkItemPromo, LargeOrderPromo등 총 3개의 구체적인 전략이 구현
전략 패턴의 예는 고객의 속성이나 주문한 상품에 따라 할인을 계산하는 전자상거래 영역에서 쉽게 볼 수 있다.
from abc import ABC, abstractmethod
from collections import namedtuple
Customer = namedtuple('Customer', 'name fidelity')
class LineItem:
def __init__(self, product, quantity, price):
self.product = product
self.quantity = quantity
self.price = price
def total(self):
return self.price * self.quantity
class Order: #콘텍스트
def __init__(self, customer, cart, promotion=None):
self.customer = customer
self.cart = list(cart)
self.promotion = promotion
def total(self):
if not hasattr(self, '__total'):
self.__total = sum(item.total() for item in self.cart)
return self.__total
def due(self):
if self.promotion is None:
discount = 0
else:
discount = self.promotion.discount(self)
return self.total() - discount
def __repr__(self):
fmt = '<Order total: {:.2f} due: {:.2f}>'
return fmt.format(self.total(), self.due())
class Promotion(ABC): #전략: 츠상 베이스 클래스
@abstractmethod
def discount(self, order):
"""할인액을 구체적인 숫자로 반환한다."""
class FidelityPromo(Promotion): #첫 번째 구체적인 전략
"""충성도 포인트가 1000점 이상인 고객에게 전체 5% 할인 적용"""
def discount(self, order):
return order.total() * 0.5 if order.customer.fidelity >= 1000 else 0
class BulkItemPromo(Promotion): # 두 번째 구체적인 전략
"""20개 이상의 동일 상품을 구입하면 10% 할인 적용"""
def discount(self, order):
discount = 0
for item in order.cart:
if item.quantity >= 20:
discount += item.total() * .1
return discount
class LargeOrderPromo(Promotion): #세 번째 구체적인 전랙
"""10종류 이상의 상품을 구입하면 전체 7% 할인 적용"""
def discount(self, order):
distinct_items = {item.product for item in order.cart)
if len(distinct_items) >= 10:
return order.total() * 0.7
return
#여러 프로모션 할인을 적용해서 Order 클래스 사용하는 예
joe = Customer('John Doe',0)
ann = Customer('Ann Smith',1100)
cart = [LineItem('banana', 4,5),
LineItem('apple',10,1.5),
LineItem('watermellon',5,5.0)]
order(joe, cart. FidelityPromo())
order(ann,cart, FidelityPromo())
banana_cart = ["LineItem(str(item_code),1,1.0)
for item_code in range(10)]
Order(joe,long_order, LargeOrderPromo())
Order(joe,cart,LargeOrderPromo())