전략(Strategy) 패턴The Strategy Pattern
전략 패턴의 개념과 구현 방법The concept of the Strategy pattern and how to implement it
전략 패턴 (Strategy Pattern)
전략 패턴(Strategy Pattern)은 행동 패턴(Behavioral Pattern)에 속합니다.비슷한 기능을 정의하고 각각을 캡슐화하여 교환해서 사용할 수 있게 만드는 패턴입니다. 프로그램 실행 중에 알고리즘을 선택하고 교체할 수 있습니다.
사용 사례
- 결제 시스템: 신용카드, 현금, 포인트 등 다양한 결제 방식
- 파일 압축: ZIP, RAR 등 다양한 압축 알고리즘
- 게임 캐릭터: 걷기, 뛰기 등 다양한 이동 방식
캐릭터 이동
여기서는 게임 캐릭터의 ‘캐릭터 이동’(걷기, 뛰기 등)을 구현해보면서 전략 패턴에 대해 알아보겠습니다.
상황
게임 캐릭터를 움직여 보도록 하겠습니다. 캐릭터는 걷기, 뛰기 등의 움직임을 수행할 수 있습니다. 이러한 움직임은 게임 진행 중에 자유롭게 변경될 수 있어야 하며, 새로운 움직임을 쉽게 추가할 수 있어야 합니다.

구성
전략 패턴은 다음과 같이 구성요소를 분리합니다.
- 컨텍스트(Context): 전략을 사용하는 객체.
- 전략(Strategy): 실제 알고리즘을 구현한 클래스.
여기서는 움직임 전략을 사용하는 캐릭터 클래스가 컨텍스트, 실제 움직임 알고리즘을 구현하는 클래스들이 전략에 해당합니다.
인터페이스
먼저 모든 움직임 전략이 따라야 할 인터페이스를 정의합니다.
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)을 잘 따르는 패턴이며, 확장성 있는 시스템을 설계할 때 고려해볼 만한 패턴입니다.
- 비슷한 역할을 하는 알고리즘군이 존재하는 경우
- 런타임에 알고리즘을 선택하고 교체해야 하는 경우
- 알고리즘의 변형이 자주 발생하는 경우
다양한 정렬 알고리즘, 파일 압축 방식, 할인 정책 등 여러 상황에서 활용할 수 있습니다. 😊
The Strategy Pattern
The Strategy Pattern is a behavioral pattern. It defines a family of similar behaviors, encapsulates each one, and makes them interchangeable. It lets you choose and swap algorithms while the program is running.
Use Cases
- Payment systems: Various payment methods such as credit card, cash, and points
- File compression: Various compression algorithms such as ZIP and RAR
- Game characters: Various ways of moving, such as walking and running
Character Movement
Here, we’ll learn about the Strategy pattern by implementing ‘character movement’ (walking, running, and so on) for a game character.
The Situation
Let’s make a game character move. The character can walk, run, and so on. These movements need to be freely changeable during gameplay, and it should be easy to add new ones.

Structure
The Strategy pattern separates its components as follows.
- Context: The object that uses a strategy.
- Strategy: A class that implements the actual algorithm.
Here, the character class that uses a movement strategy is the context, and the classes that implement the actual movement algorithms are the strategies.
The Interface
First, we define the interface that every movement strategy must follow.
from abc import ABC, abstractmethod
class MovementStrategy(ABC):
@abstractmethod
def move(self):
pass
Strategy Classes
Next, we implement the concrete movement strategies. Each strategy implements its own way of moving.
class WalkStrategy(MovementStrategy):
def move(self):
print("The character walks. Speed: 5")
class RunStrategy(MovementStrategy):
def move(self):
print("The character runs. Speed: 10")
class CrawlStrategy(MovementStrategy):
def move(self):
print("The character crawls. Speed: 2")
The Character
Now we create the character class that uses a movement strategy. The character moves according to the currently set strategy. It also has a setter method, set_movement, so the movement can be changed.
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
Running It
Let’s actually create a character and test out various movements.
You can see that the same move behaves differently depending on the strategy the character holds. And through the setter method, you can switch to any other strategy you like.
# Create a character (initial strategy: walk)
character = Character(WalkStrategy())
# Walk
character.move() # Output: The character walks. Speed: 5
# Switch the strategy to run
character.set_movement(RunStrategy())
character.move() # Output: The character runs. Speed: 10
# Switch the strategy to crawl
character.set_movement(CrawlStrategy())
character.move() # Output: The character crawls. Speed: 2
Class Diagram
%%{
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
Benefits of the Strategy Pattern
Using the Strategy pattern gives you these benefits.
- You can add new strategies without modifying existing code.
- You can freely change strategies while the program is running.
- Each strategy is encapsulated independently, which makes the code easier to manage.
Caveats When Using the Strategy Pattern
The Strategy pattern also has caveats to keep in mind. Using many strategy objects is fine, but creating strategy objects carelessly can cause performance problems.
Caveat: Memory Management
class Character:
def __init__(self):
self._current_strategy = WalkStrategy()
def set_movement(self, movement_type: str):
# Creating a new strategy object every time
match movement_type
case "walk":
self._current_strategy = WalkStrategy() # Object creation cost
case "run":
self._current_strategy = RunStrategy() # Object creation cost
Solution: Use the Singleton Pattern
With the Singleton pattern, you can make sure each strategy object is created only once and then reused. As shown below, you can manage object creation by combining the Singleton pattern with the Factory pattern.
class MovementStrategyFactory:
_strategies = {} # Cache strategy objects in a class variable
@classmethod
def get_strategy(cls, movement_type: str) -> MovementStrategy:
# Reuse the strategy if it has already been created
if movement_type not in cls._strategies:
# Create the strategy object only on the first request (lazy initialization)
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]
# Usage example
class Character:
def __init__(self):
# Get the strategy through the Factory at initialization
self._current_strategy = MovementStrategyFactory.get_strategy("walk")
def set_movement(self, movement_type: str):
# Reuse through the Factory when changing strategies, too
self._current_strategy = MovementStrategyFactory.get_strategy(movement_type)
Wrapping Up
The Strategy pattern is a useful design pattern when algorithms are frequently swapped or extended. In particular, it follows the Open-Closed Principle (OCP) well and is worth considering when designing extensible systems.
- When there’s a family of algorithms that play similar roles
- When you need to choose and swap algorithms at runtime
- When variations of an algorithm come up often
You can use it in many situations, such as different sorting algorithms, file compression methods, and discount policies. 😊