티스토리 뷰
목차
Strategy Pattern
전략 패턴은 정책 패턴(Policy Pattern)이라 불리기도 하며,
소프트웨어의 실행 중 상황에 맞는 알고리즘을 선택해 실행할 수 있도록 하는 객체 지향 디자인 패턴이다.
예를 들자면 배열을 정렬하는 sort()라는 메서드가 있을 때, 요청한 알고리즘이 어떤 정렬인지에 따라
다른 로직을 실행하도록 소프트웨어를 디자인하는 것이라고 할 수 있다.
위키백과에 따르면 전략 패턴은 대략 아래와 같은 절차에 따라 구성되며,
- 복수의 알고리즘을 정의
- 정의한 알고리즘을 캡슐화
- 해당 알고리즘을 전략에 맞춰 상호 교체 가능하도록 구성
이어서 아래와 같은 세 부분으로 구성된다.
- 전략 인터페이스(Strategy Interface)
알고리즘을 정의하는 인터페이스. 일반적으로 알고리즘을 수행하는 메서드를 선언한다. - 구체적인 전략 클래스(Concrete Strategy Classes)
전략 인터페이스를 구현하는 클래스. 각각의 전략 클래스는 전략에 따라 고유한 알고리즘을 구현한다. - 컨텍스트 클래스(Context Class)
전략을 실제 사용하는 클래스. 전략 인터페이스 타입의 멤버 변수를 가지고 있으며,
이 멤버 변수를 통해 컨텍스트는 전략 클래스의 인스턴스를 참조한다.
또한 컨텍스트는 자체적으로 알고리즘을 수행하는 메서드를 가지며,
이 메서드 내에서 전략 객체의 메서드를 호출하여 알고리즘을 실행한다.
또한 이를 시퀀스 다이어그램으로 나타내면 다음과 같은 구조가 된다.
계속해서 가볍게 실제 구현을 해보자.
Implementation
시작하기 전에, 먼저 패키지 구성은 다음과 같다.
구현 순서는 패키지 순서와는 별개이다.
가장 먼저 전략 인터페이스 SortingStrategy를 정의한다.
public interface SortingStrategy {
void sort(int[] arr);
}
이어서 위 인터페이스를 구현한 두 개의 전략 클래스를 구성한다.
당연하게도 두 클래스는 서로 다른 알고리즘을 구현해야 한다.
public class BubbleSortStrategy implements SortingStrategy{
@Override
public void sort(int[] arr) {
bubbleSortImpl(arr);
}
private static void bubbleSortImpl(int[] arr) {
for (int i = 1; i < arr.length; i++) { // 배열 길이보다 하나 적은 횟수만큼 반복
for (int j = 0; j < arr.length - 1; j++) { // 비교 횟수도 마찬가지로 하나 작음
if (arr[j] > arr[j + 1]) { // 비교 후 자리바꿈
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
}
public class QuickSortStrategy implements SortingStrategy{
@Override
public void sort(int[] arr) {
quickSortImpl(arr, 0, arr.length - 1);
}
private static void quickSortImpl(int[] arr, int lo, int hi) {
if (lo >= hi) return; // 대상 배열의 크기가 1 이하일 경우 탈출
int pivot = partitionStep(arr, lo, hi); // Partition Step 진행
quickSortImpl(arr, lo, pivot - 1); // 정렬이 끝난 pivot 기준으로 배열 쪼개서 재귀호출(왼쪽)
quickSortImpl(arr, pivot + 1, hi); // 오른쪽
}
private static int partitionStep(int[] arr, int lo, int hi) {
int left = lo; // 주어진 배열의 lo, hi, pivot 설정
int right = hi;
int pivot = arr[lo];
while (left < right) {
while (arr[right] >= pivot && left < right) { // pivot 보다 작은 값을 만날 때까지 right--;
right--;
}
while (arr[left] <= pivot && left < right) { // pivot 보다 큰 값을 만날 때까지 left++
left++;
}
swap(arr, left, right); // 값을 찾으면 스왑. 못 찾으면 left = right 인 곳에서 자체 스왑
}
swap(arr, lo, left); // left = right 인 원소와 pivot 교환
return left; // pivot 이 옯겨간, 즉 정렬된 곳의 인덱스 반환
}
private static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
각 구현에 대한 자세한 설명은 아래 글에 쓰여있지만 지금은 중요하지 않다.
[Java+Python]버블 정렬(Bubble Sort)
이어서 Context 클래스를 작성한다. 해당 클래스 내부에는 전략 인터페이스가 멤버변수로 존재하며,
이를 통해 위에서 구현한 전략 객체를 주입받아 알고리즘을 실행하게 된다.
public class SortingContext {
private SortingStrategy sortingStrategy;
public void setSortingStrategy(SortingStrategy sortingStrategy) {
this.sortingStrategy = sortingStrategy;
}
public void doSort(int[] arr) {
sortingStrategy.sort(arr);
}
}
이렇게 구현이 끝났으니 실제 사용할 클래스를 구성하자.
public class Sorting {
public static void main(String[] args) {
int[] arr = {4, 16, 31, 5, 4, 17, 1, 10, 15, 3, 16, 6, 7, 2, 2, 1, 5, 13, 17, 14, 4, 0};
int[] arr2 = {4, 16, 31, 5, 4, 17, 1, 10, 15, 3, 16, 6, 7, 2, 2, 1, 5, 13, 17, 14, 4, 0};
SortingContext sortingContext = new SortingContext();
sortingContext.setSortingStrategy(new BubbleSortStrategy());
sortingContext.doSort(arr);
System.out.println(Arrays.toString(arr));
sortingContext.setSortingStrategy(new QuickSortStrategy());
sortingContext.doSort(arr2);
System.out.println(Arrays.toString(arr2));
}
}
[0, 1, 1, 2, 2, 3, 4, 4, 4, 5, 5, 6, 7, 10, 13, 14, 15, 16, 16, 17, 17, 31]
[0, 1, 1, 2, 2, 3, 4, 4, 4, 5, 5, 6, 7, 10, 13, 14, 15, 16, 16, 17, 17, 31]
Main 클래스에서 SortingContext 객체를 생성한 뒤 setSortingStrategy() 메서드를 이용해
원하는 전략 객체를 생성해 주입받는다.
이어서 doSort()메서드를 호출하면 선택한 전략을 이용해 sort() 메서드가 실행되며,
결과는 위에 보는 것처럼 정확하게 나오게 된다.
Pros and Cons
마지막으로 전략패턴의 장/단점을 정리하고 글을 마치자.
- 장점
- 유연성과 확장성, 재사용성
전략패턴은 각 알고리즘을 독립적으로 정의하고 캡슐화하여 사용하기 때문에, 알고리즘의 변경이나 추가가 간단하다.
또한 실행시 알고리즘을 교체하거나 확장할 수 있게 함으로써 유연성과 확장성, 그리고 재사용성을 확보한다. - 중복 코드 제거
위와 같은 이유로 알고리즘 간의 코드 중복이 최소화되며, 공통 코드는 전략 인터페이스로 추출할 수 있다.
이는 각 전략 클래스를 구성할 때 로직의 구체적인 구현에만 집중할 수 있게 한다. - 단일 책임 원칙
역시 마찬가지 이유로 각 알고리즘이 단일 책임 원칙을 충족한다고 볼 수 있다. - 낮은 의존성
전략 패턴은 컨텍스트 클래스와 전략 클래스 사이의 느슨한 결합을 제공하는데, 위 그림에서도 볼 수 있듯이 컨텍스트는
추상화된 전략 인터페이스에 의존하며 실제 전략은 런타임에 컨텍스트에 주입된다.
- 유연성과 확장성, 재사용성
- 단점
- 클래스 수 증가
알고리즘마다 하나의 클래스를 배정하기 때문에 클래스 수가 불필요하게 증가할 가능성이 있다.
이는 대규모 애플리케이션으로 확장 시 관리와 유지보수가 어렵다는 단점으로 작용한다. - 오버헤드
런타임에 알고리즘이 정해지는 전략패턴의 특성상 실행 시간에서 오버헤드가 생길 가능성이 있다.
또한 객체로 생성된 전략은 호출되기 전까지 메모리 공간을 차지하게 된다.
- 클래스 수 증가
'Java+Spring > Java' 카테고리의 다른 글
[Java]자바 제네릭(Generic) (0) | 2024.07.21 |
---|---|
[Java]Java17, System.err.println() (0) | 2023.06.11 |
[Java]가변 인자(varargs) (0) | 2023.05.21 |
[Java]싱글톤 패턴(Singleton Pattern) (0) | 2023.05.03 |
[Java]Map.Entry 인터페이스 (0) | 2023.03.29 |
[Java]컬렉션 프레임워크에서 제공하는 합집합, 교집합, 차집합, 부분집합 (0) | 2023.01.29 |
- Total
- Today
- Yesterday
- RX100M5
- 알고리즘
- Python
- 칼이사
- 동적계획법
- 여행
- 야경
- 스트림
- 면접 준비
- 백준
- 세모
- 기술면접
- 파이썬
- Algorithm
- 유럽
- 세계여행
- 리스트
- 스프링
- 세계일주
- 자바
- 지지
- a6000
- 맛집
- 유럽여행
- java
- Backjoon
- spring
- 중남미
- BOJ
- 남미
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |