Python 전략 패턴
Python 전략 패턴
전략 패턴(Strategy Pattern) 이해하기
디자인 패턴은 소프트웨어 개발에서 자주 등장하는 문제를 해결하기 위한 “모범 사례”를 제공합니다. 이 중, 전략 패턴(Strategy Pattern)은 객체의 행위를 동적으로 변경하거나 확장하고 싶을 때 매우 유용합니다.
전략 패턴이란?
전략 패턴은 행위(Behavioral) 디자인 패턴 중 하나로, 행위를 캡슐화하여 독립적으로 정의하고, 런타임에서 필요한 전략으로 교체할 수 있도록 설계된 패턴입니다. 즉, “동작”을 변경하기 위해 클래스 전체를 수정할 필요 없이, 독립적인 전략 클래스를 추가하여 동작을 바꿀 수 있습니다.
핵심 구조
- 전략(Strategy) 인터페이스: 공통 동작의 정의를 포함합니다.
- 구체적인 전략(Concrete Strategy): 각기 다른 동작을 실제로 구현합니다.
- 컨텍스트(Context): 전략 객체를 사용하는 클래스이며, 전략을 동적으로 교체할 수 있습니다.
왜 전략 패턴을 사용해야 할까?
코드의 유연성과 확장성 행동을 클래스에서 독립적으로 정의함으로써, 기존 코드를 수정하지 않고도 새로운 행동(전략)을 쉽게 추가할 수 있습니다.
중복 제거와 코드 재사용성 유사한 행동을 분리된 클래스로 정의하여 중복 코드를 제거하고, 여러 곳에서 재사용할 수 있습니다.
OCP(Open-Closed Principle) 준수 기존 코드에 영향을 주지 않고 새로운 전략을 추가할 수 있어 확장에는 열려 있고, 수정에는 닫혀 있는 설계를 따릅니다.
열려 있는 폐쇄 원칙(Open-Closed Principle, OCP)은 객체 지향 설계의 SOLID 원칙 중 하나로, 소프트웨어 시스템을 설계할 때 다음을 목표로 합니다:
- 확장에는 열려 있어야(Open): 새로운 기능이나 동작을 추가할 수 있도록 설계되어야 한다.
- 수정에는 닫혀 있어야(Closed): 기존의 코드(클래스, 메서드 등)를 수정하지 않아야 한다.
전략 패턴을 사용해야 하는 경우
- 클래스에서 여러 동작이 조건문으로 분기 처리될 때.
- 예:
if
,elif
문이 많아지면서 코드가 복잡하고 유지보수가 어려워질 때.
- 예:
- 런타임에 객체의 행위를 변경해야 할 때.
- 예: 사용자 입력이나 외부 조건에 따라 동작을 바꿔야 하는 경우.
- 특정 동작을 여러 곳에서 재사용해야 할 때.
- 예: 공통된 인터페이스를 기반으로 다양한 구현이 필요한 경우.
Python으로 전략 패턴 구현하기
아래는 전략 패턴을 Python으로 구현하는 간단한 예제입니다.
예제: 결제 시스템의 다양한 결제 방법 구현
from abc import ABC, abstractmethod
# 1. 전략 인터페이스 정의
class PaymentStrategy(ABC):
@abstractmethod
def pay(self, amount: float) -> None:
pass
# 2. 구체적인 전략 구현
class CreditCardPayment(PaymentStrategy):
def __init__(self, card_number: str):
self.card_number = card_number
def pay(self, amount: float) -> None:
print(f"Paying ${amount} using Credit Card: {self.card_number}")
class PayPalPayment(PaymentStrategy):
def __init__(self, email: str):
self.email = email
def pay(self, amount: float) -> None:
print(f"Paying ${amount} using PayPal: {self.email}")
class BitcoinPayment(PaymentStrategy):
def __init__(self, wallet_address: str):
self.wallet_address = wallet_address
def pay(self, amount: float) -> None:
print(f"Paying ${amount} using Bitcoin Wallet: {self.wallet_address}")
# 3. 컨텍스트 클래스
class PaymentProcessor:
def __init__(self, strategy: PaymentStrategy):
self.strategy = strategy
def set_strategy(self, strategy: PaymentStrategy) -> None:
self.strategy = strategy
def process_payment(self, amount: float) -> None:
self.strategy.pay(amount)
# 4. 전략 패턴 사용 예시
if __name__ == "__main__":
# 초기 설정: PayPal 결제
= PaymentProcessor(PayPalPayment("user@example.com"))
payment 50.0)
payment.process_payment(
# 전략 교체: 신용카드 결제
"1234-5678-9012-3456"))
payment.set_strategy(CreditCardPayment(100.0)
payment.process_payment(
# 전략 교체: 비트코인 결제
"1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"))
payment.set_strategy(BitcoinPayment(200.0) payment.process_payment(
전략 패턴은 행위를 캡슐화하여 특정 행동(전략)을 독립된 클래스로 구현하고, 런타임에 행동을 동적으로 변경할 수 있도록 하는 디자인 패턴입니다. 전략 패턴은 다음과 같은 구성 요소로 이루어집니다:
- 전략 인터페이스(Strategy Interface): 공통된 행동을 정의합니다.
- 구체적인 전략(Concrete Strategy): 각기 다른 행동을 실제로 구현합니다.
- 컨텍스트(Context): 전략 객체를 사용하며, 전략을 동적으로 교체할 수 있습니다.
1. 전략 인터페이스 정의
from abc import ABC, abstractmethod
class PaymentStrategy(ABC):
@abstractmethod
def pay(self, amount: float) -> None:
pass
PaymentStrategy
는 전략 인터페이스입니다.- 모든 결제 방식은
pay
메서드를 구현해야 하며, 결제 금액(amount
)을 매개변수로 받습니다. ABC
와@abstractmethod
를 사용하여 추상 클래스를 정의했습니다.
2. 구체적인 전략 구현
class CreditCardPayment(PaymentStrategy):
def __init__(self, card_number: str):
self.card_number = card_number
def pay(self, amount: float) -> None:
print(f"Paying ${amount} using Credit Card: {self.card_number}")
class PayPalPayment(PaymentStrategy):
def __init__(self, email: str):
self.email = email
def pay(self, amount: float) -> None:
print(f"Paying ${amount} using PayPal: {self.email}")
class BitcoinPayment(PaymentStrategy):
def __init__(self, wallet_address: str):
self.wallet_address = wallet_address
def pay(self, amount: float) -> None:
print(f"Paying ${amount} using Bitcoin Wallet: {self.wallet_address}")
CreditCardPayment
,PayPalPayment
,BitcoinPayment
는 구체적인 전략(Concrete Strategy)들입니다.- 각 클래스는
PaymentStrategy
인터페이스를 구현하며, 결제 방식을 다르게 처리합니다.CreditCardPayment
: 신용카드로 결제.PayPalPayment
: 페이팔로 결제.BitcoinPayment
: 비트코인 지갑으로 결제.
3. 컨텍스트 클래스
class PaymentProcessor:
def __init__(self, strategy: PaymentStrategy):
self.strategy = strategy
def set_strategy(self, strategy: PaymentStrategy) -> None:
self.strategy = strategy
def process_payment(self, amount: float) -> None:
self.strategy.pay(amount)
PaymentProcessor
는 컨텍스트(Context)로, 결제 방식(전략)을 사용합니다.set_strategy
메서드를 통해 결제 전략을 런타임에 변경할 수 있습니다.process_payment
메서드는 현재 설정된 전략 객체를 사용하여 결제를 처리합니다.
4. 전략 패턴 사용 예시
if __name__ == "__main__":
# 초기 설정: PayPal 결제
= PaymentProcessor(PayPalPayment("user@example.com"))
payment 50.0)
payment.process_payment(
# 전략 교체: 신용카드 결제
"1234-5678-9012-3456"))
payment.set_strategy(CreditCardPayment(100.0)
payment.process_payment(
# 전략 교체: 비트코인 결제
"1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"))
payment.set_strategy(BitcoinPayment(200.0) payment.process_payment(
- 처음에는
PayPalPayment
전략을 사용하여 결제를 처리합니다. - 이후
set_strategy
를 호출하여CreditCardPayment
로 결제 방식을 변경합니다. - 마지막으로
BitcoinPayment
로 전략을 교체하여 결제를 처리합니다.
출력 결과:
Paying $50.0 using PayPal: user@example.com
Paying $100.0 using Credit Card: 1234-5678-9012-3456
Paying $200.0 using Bitcoin Wallet: 1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa
전략 패턴의 장점
- 새로운 결제 방법을 추가하려면 기존 코드에 영향을 주지 않고 새로운 클래스를 추가하기만 하면 됩니다.
- 조건문이나 분기 처리 없이 결제 방식을 유연하게 변경할 수 있습니다.
전략 패턴의 단점
- 클래스 수가 늘어나며 복잡도가 증가할 수 있습니다.
- 간단한 문제라면 오히려 과도한 설계가 될 수 있습니다.
마무리
전략 패턴은 동작을 쉽게 확장하고 교체할 수 있도록 설계되어, 유연하고 확장 가능한 시스템 구축에 적합합니다. 위의 Python 예제를 바탕으로, 조건문 대신 전략 패턴을 활용해 더 깔끔한 코드를 작성해보세요!