Python 전략 패턴

Python 전략 패턴

DesignPattern
Python 전략 패턴
Author

gabriel yang

Published

November 25, 2024


전략 패턴(Strategy Pattern) 이해하기

디자인 패턴은 소프트웨어 개발에서 자주 등장하는 문제를 해결하기 위한 “모범 사례”를 제공합니다. 이 중, 전략 패턴(Strategy Pattern)은 객체의 행위를 동적으로 변경하거나 확장하고 싶을 때 매우 유용합니다.

전략 패턴이란?

전략 패턴은 행위(Behavioral) 디자인 패턴 중 하나로, 행위를 캡슐화하여 독립적으로 정의하고, 런타임에서 필요한 전략으로 교체할 수 있도록 설계된 패턴입니다. 즉, “동작”을 변경하기 위해 클래스 전체를 수정할 필요 없이, 독립적인 전략 클래스를 추가하여 동작을 바꿀 수 있습니다.

핵심 구조

  1. 전략(Strategy) 인터페이스: 공통 동작의 정의를 포함합니다.
  2. 구체적인 전략(Concrete Strategy): 각기 다른 동작을 실제로 구현합니다.
  3. 컨텍스트(Context): 전략 객체를 사용하는 클래스이며, 전략을 동적으로 교체할 수 있습니다.

왜 전략 패턴을 사용해야 할까?

  1. 코드의 유연성과 확장성 행동을 클래스에서 독립적으로 정의함으로써, 기존 코드를 수정하지 않고도 새로운 행동(전략)을 쉽게 추가할 수 있습니다.

  2. 중복 제거와 코드 재사용성 유사한 행동을 분리된 클래스로 정의하여 중복 코드를 제거하고, 여러 곳에서 재사용할 수 있습니다.

  3. OCP(Open-Closed Principle) 준수 기존 코드에 영향을 주지 않고 새로운 전략을 추가할 수 있어 확장에는 열려 있고, 수정에는 닫혀 있는 설계를 따릅니다.

Note

열려 있는 폐쇄 원칙(Open-Closed Principle, OCP)은 객체 지향 설계의 SOLID 원칙 중 하나로, 소프트웨어 시스템을 설계할 때 다음을 목표로 합니다:

  • 확장에는 열려 있어야(Open): 새로운 기능이나 동작을 추가할 수 있도록 설계되어야 한다.
  • 수정에는 닫혀 있어야(Closed): 기존의 코드(클래스, 메서드 등)를 수정하지 않아야 한다.

전략 패턴을 사용해야 하는 경우

  1. 클래스에서 여러 동작이 조건문으로 분기 처리될 때.
    • 예: if, elif 문이 많아지면서 코드가 복잡하고 유지보수가 어려워질 때.
  2. 런타임에 객체의 행위를 변경해야 할 때.
    • 예: 사용자 입력이나 외부 조건에 따라 동작을 바꿔야 하는 경우.
  3. 특정 동작을 여러 곳에서 재사용해야 할 때.
    • 예: 공통된 인터페이스를 기반으로 다양한 구현이 필요한 경우.

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 결제
    payment = PaymentProcessor(PayPalPayment("user@example.com"))
    payment.process_payment(50.0)

    # 전략 교체: 신용카드 결제
    payment.set_strategy(CreditCardPayment("1234-5678-9012-3456"))
    payment.process_payment(100.0)

    # 전략 교체: 비트코인 결제
    payment.set_strategy(BitcoinPayment("1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"))
    payment.process_payment(200.0)

전략 패턴은 행위를 캡슐화하여 특정 행동(전략)을 독립된 클래스로 구현하고, 런타임에 행동을 동적으로 변경할 수 있도록 하는 디자인 패턴입니다. 전략 패턴은 다음과 같은 구성 요소로 이루어집니다:

  1. 전략 인터페이스(Strategy Interface): 공통된 행동을 정의합니다.
  2. 구체적인 전략(Concrete Strategy): 각기 다른 행동을 실제로 구현합니다.
  3. 컨텍스트(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 결제
    payment = PaymentProcessor(PayPalPayment("user@example.com"))
    payment.process_payment(50.0)

    # 전략 교체: 신용카드 결제
    payment.set_strategy(CreditCardPayment("1234-5678-9012-3456"))
    payment.process_payment(100.0)

    # 전략 교체: 비트코인 결제
    payment.set_strategy(BitcoinPayment("1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"))
    payment.process_payment(200.0)
  1. 처음에는 PayPalPayment 전략을 사용하여 결제를 처리합니다.
  2. 이후 set_strategy를 호출하여 CreditCardPayment로 결제 방식을 변경합니다.
  3. 마지막으로 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 예제를 바탕으로, 조건문 대신 전략 패턴을 활용해 더 깔끔한 코드를 작성해보세요!