디자인 패턴 - 전략 패턴 (Strategy Pattern)

파이썬에서 전략(Strategy) 패턴을 배워 봅시다!

written by tiaz0128

전략 패턴 (Strategy Pattern)


전략 패턴(Strategy Pattern)은 행동 패턴(Behavioral Pattern)에 속합니다.비슷한 기능을 정의하고 각각을 캡슐화하여 교환해서 사용할 수 있게 만드는 패턴입니다. 프로그램 실행 중에 알고리즘을 선택하고 교체할 수 있습니다.

사용 사례

캐릭터 이동


여기서는 게임 캐릭터의 ‘캐릭터 이동’(걷기, 뛰기 등)을 구현해보면서 전략 패턴에 대해 알아보겠습니다.

상황

게임 캐릭터를 움직여 보도록 하겠습니다. 캐릭터는 걷기, 뛰기 등의 움직임을 수행할 수 있습니다. 이러한 움직임은 게임 진행 중에 자유롭게 변경될 수 있어야 하며, 새로운 움직임을 쉽게 추가할 수 있어야 합니다.

게임 캐릭터

구성

전략 패턴은 다음과 같이 구성요소를 분리합니다.

여기서는 움직임 전략을 사용하는 캐릭터 클래스가 컨텍스트, 실제 움직임 알고리즘을 구현하는 클래스들이 전략에 해당합니다.

인터페이스

먼저 모든 움직임 전략이 따라야 할 인터페이스를 정의합니다.

from abc import ABC, abstractmethod

class MovementStrategy(ABC):
    @abstractmethod
    def move(self):
        pass

전략 클래스(Strategy class)

다음으로 구체적인 움직임 전략들을 구현합니다. 각각의 전략은 자신만의 고유한 이동 방식을 구현합니다.

class WalkStrategy(MovementStrategy):
    def move(self):
        print("캐릭터가 걸어서 이동합니다. 이동속도: 5")

class RunStrategy(MovementStrategy):
    def move(self):
        print("캐릭터가 뛰어서 이동합니다. 이동속도: 10")

class CrawlStrategy(MovementStrategy):
    def move(self):
        print("캐릭터가 기어서 이동합니다. 이동속도: 2")

캐릭터

움직임 전략을 사용하는 캐릭터 클래스를 만듭니다. 캐릭터는 현재 설정된 전략에 따라 이동합니다. 그리고 움직임을 변경 할 수 있게, Setter 메서드 set_movement를 가지고 있습니다.

class Character:
    def __init__(self, strategy: MovementStrategy):
        self.strategy = strategy
    
    def move(self):
        return self.strategy.move()
    
    def set_movement(self, strategy: MovementStrategy):
        self.strategy = strategy

실행

이제 실제로 캐릭터를 생성하고 다양한 움직임을 테스트해봅시다.

캐릭터가 가지고 있는 전략에 따라서 같은 이동이 다르게 동작하는 것을 알 수 있습니다. 그리고 Setter 메서드를 통해, 원하는 다른 전략으로 변경할 수 있습니다.

# 캐릭터 생성 (초기 전략: 걷기)
character = Character(WalkStrategy())

# 걷기 실행
character.move()  # 출력: 캐릭터가 걸어서 이동합니다. 이동속도: 5

# 전략을 뛰기로 변경
character.set_movement(RunStrategy())
character.move()  # 출력: 캐릭터가 뛰어서 이동합니다. 이동속도: 10

# 전략을 기어가기로 변경
character.set_movement(CrawlStrategy())
character.move()  # 출력: 캐릭터가 기어서 이동합니다. 이동속도: 2

클래스 다이어그램


%%{
  init: {
    'theme': 'base',
    'themeVariables': {
      'primaryColor': '#2a3844',
      'lineColor': '#fff',
      'primaryTextColor': '#fff',
      'tertiaryColor': '#fff'
    }
  }
}%%
classDiagram
    direction LR

    class MovementStrategy {
        <<interface>>
        +move()
    }
    
    class WalkStrategy {
        +move()
    }
    
    class RunStrategy {
        +move()
    }
    
    class CrawlStrategy {
        +move()
    }
    
    class Character {
        -strategy: MovementStrategy
        +\_\_init\_\_(strategy: MovementStrategy)
        +move()
        +set_movement(strategy: MovementStrategy)
    }
    
    MovementStrategy <|.. WalkStrategy
    MovementStrategy <|.. RunStrategy
    MovementStrategy <|.. CrawlStrategy
    Character o-- MovementStrategy

전략 패턴의 장점


전략 패턴을 사용하면 다음과 같은 이점이 있습니다.

전략 패턴 사용 시 주의사항


전략 패턴도 사용 시 고려해야 할 주의사항이 있습니다. 다양한 전략 객체를 사용하는 것은 좋지만, 반대로 무분별하게 전략 객체를 생성하는 경우에는 성능상 문제가 발생 할 수 있습니다.

주의 사항: 메모리 관리

class Character:
    def __init__(self):
        self._current_strategy = WalkStrategy()

    def set_movement(self, movement_type: str):
        # 매번 새로운 전략 객체를 생성하는 경우
        match movement_type 
            case "walk":
                self._current_strategy = WalkStrategy()  # 객체 생성 비용 발생
            case "run":
                self._current_strategy = RunStrategy()   # 객체 생성 비용 발생

해결 방법: 싱글턴 패턴 활용

싱글턴 패턴을 사용하여 각 전략 객체가 단 한 번만 생성되어 재사용하게 만들어 줄 수 있습니다. 아래와 같이 싱글턴 패턴과 팩토리 패턴을 활용하여 객체 생성을 관리 할 수 있습니다.

class MovementStrategyFactory:
    _strategies = {}  # 클래스 변수로 전략 객체들을 캐싱
    
    @classmethod
    def get_strategy(cls, movement_type: str) -> MovementStrategy:
        # 이미 생성된 전략이 있다면 재사용
        if movement_type not in cls._strategies:
            # 처음 요청될 때만 전략 객체 생성 (지연 초기화)
            match movement_type 
                case "walk":
                    cls._strategies[movement_type] = WalkStrategy()
                case "run":
                    cls._strategies[movement_type] = RunStrategy()
                case "crawl":
                    cls._strategies[movement_type] = CrawlStrategy()
        return cls._strategies[movement_type]


# 사용 예시
class Character:
    def __init__(self):
        # 초기화 시점에 Factory를 통해 전략 획득
        self._current_strategy = MovementStrategyFactory.get_strategy("walk")
    
    def set_movement(self, movement_type: str):
        # 전략 변경 시에도 Factory를 통해 재사용
        self._current_strategy = MovementStrategyFactory.get_strategy(movement_type)

마무리


전략 패턴은 알고리즘의 교체와 확장이 자주 일어나는 상황에서 유용한 디자인 패턴입니다. 특히 개방-폐쇄 원칙(OCP)을 잘 따르는 패턴이며, 확장성 있는 시스템을 설계할 때 고려해볼 만한 패턴입니다.

  1. 비슷한 역할을 하는 알고리즘군이 존재하는 경우
  2. 런타임에 알고리즘을 선택하고 교체해야 하는 경우
  3. 알고리즘의 변형이 자주 발생하는 경우

다양한 정렬 알고리즘, 파일 압축 방식, 할인 정책 등 여러 상황에서 활용할 수 있습니다. 😊

python 디자인 패턴 팩토리

tiaz0128

Eat Sleep Coding.

Never Never GiveUp.

Security  |  BackEnd  |  Multi Cloud