물건을 사거나 팔 때, 결제를 할 때, 로그를 남길 때 등 많은 상황에서 각각의 상황에 맞는 선택지중 하나를 골라야 한다. 전략 패턴은 이런 선택의 기로에서 유용하게 사용되는 디자인 패턴중 하나이다.
전략패턴은 여러 가지의 알고리즘이나 동작을 동일한 인터페이스를 가진 전략 클래스로 구현하고, 실행 시점에 원하는 전략(알고리즘)을 선택하여 사용하는 패턴이다. 이를 통해 실행 시점에 다양한 전략을 쉽게 교체하거나 확장할 수 있다.
전략패턴의 핵심은 인터페이스를 통한 다형성이다. 각각의 전략은 동일한 인터페이스를 따르기 때문에 클라이언트는 어떤 전략을 선택하든 일관된 방법으로 해당 전략을 사용할 수 있다. 또한 실행시점에 다양한 전략을 선택할 수 있어 알고리즘이나 동작을 유연하게 변경하거나 확장할 수 있다. (OCP 원칙을 잘 지킬수 있다)
전략 패턴은 코드의 재사용성, 확장성, 유지보수성을 향상시키는데 도움을 주는 강력한 디자인패턴으로 다양한 상황에서 선택의 기로에 서는 경우에 유용하게 사용될 수 잇다.
다양한 정렬 알고리즘을 동일한 인터페이스를 가진 전략 클래스로 구현하고, 실행 시점에 원하는 알고리즘을 선택하여 사용할 수 있다.
온라인 쇼핑몰에서 다양한 결제 방법(신용카드, 페이팔, 은행이체 등)을 동일한 인터페이스를 가진 전략 클래스로 구현하고, 고객이 결제 방법에 따라 해당 전략을 실행하여 결제를 처리할 수 있다.
로깅 시스템에서 다양한 로깅레벨(디버그, 인포, 에러 등)을 동일한 인터페이스를 가진 전략 클래스로 구현하고, 로깅 레벨에 따라 해당 전략을 선택하여 로그를 기록할 수 있다.
게임에서 다양한 공격 방식(근접공격, 원거리공격, 마법공격 등)을 동일한 인터페이스를 가진 전략 클래스로 구현하고, 플레에어가 선택한 공격 방식에 따라 해당 전략을 실행하여 게임 캐릭터의 공격을 처리할 수 있다.
전략 클래스는 각각의 다양한 알고리즘이나 동작을 구현하는 클래스이다. 전략 클래스를 어떻게 구현할 것인지, 어떤 디자인 원칙을 따를것인지 등을 고려해야한다. 또한 전략 클래스 간에 공통된 원칙이 있다면 이를 추상화하여 인터페이스 또는 추상 클래스를 사용할 수 있다.
클라이언트는 전략을 실행하는 역할을 담당한다. 클라이언트는 전략 클래스와의 결합도를 낮추기위해 인터페이스를 통해 전략을 사용하고 실행 시점에 전략을 교체하는 방법을 이해해야 한다.
전략을 선택하는 방법에 대해서도 고려해야 한다. 예를 들어, 클라이언트가 전략을 하드코딩하는 방식이 아니라 설정파일, 사용자입력, 외부 서비스 등을 통해 동적으로 전략을 선택할 수 있어야 한다.
전략 패턴은 다양한 상황에서 활용된다. 다양한 동작을 다형성을 통해 유연하게 변경하고자 할 때 사용할 수 있다. 전략패턴을 다양한 상황에 적용하여 활용하는 방법을 공부하고 이해하는 것이 중요하다.
전략 패턴은 OCP 원칙을 따르는 디자인 패턴이다. 수정이 필요한 부분은 최소화하고, 확장에는 열려있고 수정에는 닫혀있는 설계를 지향하는 원칙으로, 전략패턴을 적용하여 OCP 원칙을 만족하는 설계를 구현하는 방법을 이해하고 활용하는 것이 중요하다.
정렬 알고리즘을 선택하는 상황을 가정하여 전략 패턴을 실제로 적용해보았다. 정렬 알고리즘을 선택하는 상황에서 전략 패턴을 적용하는 순서는 다음과 같을 것이다.
각각의 정렬 알고리즘을 구현하는 전략 클래스가 공통된 인터페이스 또는 추상 클래스를 확장할 수 있도록 정의한다. SortStrategy 라는 인터페이스를 정의한다.
선택 가능한 다양한 정렬 알고리즘을 각각의 전략 클래스로 구현한다. 정렬 알고리즘 구현하기 귀찮으니까 인터넷에서 가져오기 ㅋㅋㅋ
정렬 알고리즘을 선택하는 클라이언트 구현, 클라이언트는 전략 인터페이스를 통해 전략을 사용하고 실행 시점에 다양한 정렬 알고리즘을 선택할 수 있어야한다.
package my.sort;
// 1. 전략 인터페이스 정의
public interface SortStrategy {
int[] sort(int[] arr);
}
// 2. 다양한 정렬 알고리즘 클래스 구현
class BubbleSortStrategy implements SortStrategy{
@Override
public int[] sort(int[] arr) {
System.out.println("버블정렬 알고리즘 선택");
// 버블정렬 알고리즘 실행
return arr;
}
}
class QuickSortStrategy implements SortStrategy{
@Override
public int[] sort(int[] arr) {
System.out.println("퀵정렬 알고리즘 선택");
// 퀵정렬 알고리즘 실행
return arr;
}
}
// 3. 클라이언트 클래스 구현
class Client {
private final SortStrategy sortStrategy;
public Client(SortStrategy sortStrategy) {
this.sortStrategy = sortStrategy;
}
void sort(int[] arr) {
sortStrategy.sort(arr);
}
}
class Main {
public static void main(String[] args) {
int[] arr = {5,4,3,2,1};
SortStrategy bubbleSortStrategy = new BubbleSortStrategy();
Client client1 = new Client(bubbleSortStrategy);
client1.sort(arr);
SortStrategy quickSortStrategy = new QuickSortStrategy();
Client client2 = new Client(quickSortStrategy);
client2.sort(arr);
}
}
전략 패턴을 사용하여 다양한 알고리즘을 선택할 수 있는 코드를 만들었다. 전략 패턴을 사용하면 새로운 정렬 알고리즘 요구되는 상황에서 쉽게 수정할 수 있다. 만약 병합정렬 알고리즘을 사용하고 싶으면 MergeSortStrategy 클래스만 추가하기만 하면 된다.
전략패턴은 알고리즘을 캡슐화하고 클라이언트 객체와 클래스의 변경을 최소화 할 수 있다. 이는 시스템의 유연성을 높이고 확장성을 강화하는 장점을 가진다.
class Main {
public static void main(String[] args) {
int[] arr = {5,4,3,2,1};
SortStrategy bubbleSortStrategy = new BubbleSortStrategy();
Client client1 = new Client(bubbleSortStrategy);
client1.sort(arr);
SortStrategy quickSortStrategy = new QuickSortStrategy();
Client client2 = new Client(quickSortStrategy);
client2.sort(arr);
}
}
이렇게 전략패턴을 사용하면 클라이언트 객체가 실행할 전략 객체를 주입받아 사용하기 때문에 DI의 개념을 따르게 된다. 위와같이 다양한 정렬 알고리즘을 선택하는 경우 클라이언트 객체는 전략 인터페이스를 주입받아 사용하므로 의존성을 외부에서 주입하는 형태로 DI를 구현한 것이라고 볼 수 있다.
하지만 전략패턴 == DI는 아니다. DI는 객체간 의존성을 관리하는 일반적인 개념이고, 여러 디자인 패턴과 개념을 함께 사용하여 유연하고 확장 가능한 소프트웨어를 개발하는 개념이다.
좋은 글이네요 ^^ 영감 받고 갑니다!!