전략 패턴
알고리즘의 실행 방법을 변경할 수 있게 해주는 패턴. 동일 계열의 알고리즘을 정의하고, 각 알고리즘을 캡슐화하며, 이들을 상호교환이 가능하도록 만든다.
구조
- Strategy - 제공하는 모든 알고리즘에 대한 공통된 연산들을 인터페이스로 정의한다.
- ConcreteStrategy - Strategy 인터페이스를 실제 알고리즘으로 구현한다.
- Context - Strategy 객체를 사용하는 클래스. 필요에 따라 Strategy 서브 클래스에 접근하는 방법을 정의한다.
기본 인터페이스
// 전략 인터페이스 정의
public interface IStrategy
{
void Execute();
}
// 구체적인 전략을 구현하는 클래스들
public class ConcreteStrategyA : IStrategy
{
public void Execute()
{
Console.WriteLine("Executing ConcreteStrategyA");
}
}
public class ConcreteStrategyB : IStrategy
{
public void Execute()
{
Console.WriteLine("Executing ConcreteStrategyB");
}
}
public class ConcreteStrategyC : IStrategy
{
public void Execute()
{
Console.WriteLine("Executing ConcreteStrategyC");
}
}
// 전략 객체를 사용하는 컨텍스트 클래스
public class Context
{
private IStrategy _strategy;
public Context(IStrategy strategy)
{
this._strategy = strategy;
}
public void SetStrategy(IStrategy strategy)
{
this._strategy = strategy;
}
public void ExecuteStrategy()
{
_strategy.Execute();
}
}
// 클라이언트 코드
class Program
{
static void Main(string[] args)
{
var context = new Context(new ConcreteStrategyA());
context.ExecuteStrategy();
context.SetStrategy(new ConcreteStrategyB());
context.ExecuteStrategy();
context.SetStrategy(new ConcreteStrategyC());
context.ExecuteStrategy();
}
}
왜 사용해야 할까?
전략 패턴은 어떤 일을 수행하는 방법이 여러가지일 때, 여러 알고리즘을 각각의 개별적인 클래스로 캡슐화하고 캡슐화된 공통된 인터페이스로 추상화해서 로직을 수행하는 곳에서 추상화된 인터페이스를 사용함으로써 코드의 변경 없이 필요한 알고리즘으로 변경할 수 있는 패턴이다.
이해를 위해 간단한 클래스를 작성해보자. 우리는 여러 가지 정렬 알고리즘을 사용하는 클래스를 작성하고 싶다. 정렬 알고리즘 클래스에는 여러 정렬 알고리즘에 대한 메서드들이 구현되어 있을 것이고, 클라이언트는 해당 클래스를 통해 알고리즘을 사용할 것이다.
class Sorter
{
public void BubbleSort(int[] data)
{
// 여기에 버블 정렬 알고리즘 구현이 들어갈 수 있지만, 이 예제에서는 메시지만 출력합니다.
Console.WriteLine("버블 정렬 사용함!");
}
public void MergeSort(int[] data)
{
Console.WriteLine("병합 정렬 사용함!");
}
}
class Program
{
static void Main()
{
Sorter sorter = new Sorter();
int[] data = { 5, 3, 8, 4, 2 };
// 각 정렬 알고리즘을 사용
sorter.BubbleSort(data);
sorter.MergeSort(data);
}
}
이렇게만 만들어도 충분히 정렬 알고리즘 클래스를 구현한 것이라고 느껴진다. 그런데 만약 정렬 알고리즘을 추가해야한다면 어떻게 될까? 기능의 추가를 위해 클래스의 수정이 불가피할 것이다. 이는 객체지향 원칙의 OCP(개방 폐쇄 원칙)을 위배한다.
또한 클래스가 확장하여 메서드가 중복될 경우, 기존 메서드의 수정이 요구된다. 이 때문에 기존 클라이언트 코드까지 수정해야 하는 문제가 발생할 수 있다.
class Sorter
{
/*.
.
.*/
//비슷한 기능의 여러 메서드가 필요하여 메서드명이 수정이 필요할 경우, 클라이언트 코드까지 변경해야 한다.
public void MergeSort_Prototype(int[] data)
{
Console.WriteLine("병합 정렬 사용함!");
}
//기능의 추가를 위해 기존 클래스를 수정해야 한다.
public void QuickSort(int[] data)
{
Console.WriteLine("퀵 정렬 사용함!");
}
}
class Program
{
static void Main()
{
Sorter sorter = new Sorter();
int[] data = { 5, 3, 8, 4, 2 };
sorter.QuickSort(data);
//Error!
sorter.MergeSort(data);
}
}
전략 패턴을 적용하면 기존 클래스와 메서드들을 수정할 필요가 없어진다. 전략에 해당하는 알고리즘들은 공통된 알고리즘 인터페이스를 상속받아 각각의 클래스로 구현할 수 있다.
//추상화된 전략 인터페이스
public interface ISortStrategy
{
void Sort(int[] data);
}
//전략 클래스
public class BubbleSortStrategy : ISortStrategy
{
public void Sort(int[] data)
{
Console.WriteLine("버블 정렬 사용함!");
}
}
/*...전략 클래스 추가...*/
Sorter 클래스는 알고리즘 클래스들의 추상화된 인터페이스를 통해 원하는 전략을 받아 사용하면 된다. 버블 정렬을 사용하고 싶다면 버블정렬 알고리즘 클래스를, 퀵 정렬을 사용하고 싶다면 퀵 정렬 알고리즘 클래스를 받아 사용하면 되는 것이다!
public class Sorter
{
//전략 인터페이스
private ISortStrategy _sortStrategy;
//전략 선택
public void SetSortStrategy(ISortStrategy sortStrategy)
{
_sortStrategy = sortStrategy;
}
//정렬 알고리즘 실행
public void Sort(int[] data)
{
_sortStrategy.Sort(data);
}
}
class Program
{
static void Main()
{
int[] data = { 5, 3, 8, 4, 2 };
// 버블 정렬 전략 사용
Sorter sorter = new Sorter();
sorter.SetSortStrategy(new BubbleSortStrategy());
sorter.Sort(data);
// 전략 변경: 병합 정렬 전략 사용
sorter.SetSortStrategy(new MergeSortStrategy());
sorter.Sort(data);
}
}
이후 새 정렬 알고리즘을 추가할 경우, 기존 클래스들을 수정할 필요 없이 전략 인터페이스를 통해 추가 정렬 알고리즘 클래스를 구현하면 되기 때문에 OCP를 준수할 수 있다.
//정렬 알고리즘 추가
public class QuickSortStrategy : ISortStrategy
{
public void Sort(int[] data)
{
Console.WriteLine("퀵 정렬 사용함!");
}
}
class Program
{
static void Main()
{
int[] data = { 5, 3, 8, 4, 2 };
// 퀵 정렬 전략 사용
Sorter sorter = new Sorter(new QuickSortStrategy());
sorter.Sort(data);
}
}
장점
- 새로운 전략을 추가하더라도 기존 코드를 변경하지 않는다.
- 상속 대신 위임을 사용할 수 있다.
- 런타임 중 객체의 전략을 동적으로 변경할 수 있다.
단점
- 코드 복잡도가 증가한다.
- 클라이언트 코드가 구체적인 전략을 알아야 한다.
전체 예시 코드
참고자료
'프로그래밍 이론 > 디자인 패턴' 카테고리의 다른 글
19. [Design Pattern] 메멘토(Memento) 패턴 (0) | 2024.02.19 |
---|---|
18. [Design Pattern] 중재자(Mediator) 패턴 (0) | 2024.01.22 |
16. [Design Pattern] 이터레이터(Iterator) 패턴 (0) | 2024.01.14 |
15. [Design Pattern] 인터프리터(interpreter) 패턴 (0) | 2024.01.14 |
14. [Design Pattern] 커맨드(Command) 패턴 (0) | 2024.01.09 |