Strategy Pattern
Strategy 전략 패턴은 실행(런타임) 중에 알고리즘 전략을 선택하여
객체 동작을 실시간으로 바뀌도록 할 수 있게 하는 행위 디자인 패턴이다.
여기서 '전략'이란 일종의 알고리즘이 될 수 도 있으며,
기능이나 동작이 될 수도 있는 특정한 목표를 수행하기 위한 행동 계획을 말한다.
즉, 어떤 일을 수행하는 알고리즘이 여러 가지 일 때,
동작들을 미리 전략으로 정의함으로써 손쉽게 전략을 교체할 수 있는,
알고리즘 변형이 빈번하게 필요한 경우에 적합한 패턴이다.
이 패턴을 정책이라고도 하며, Strategy Pattern은 Open/Closed 원리를 기반으로 한다.
메인 Context를 수정할 필요 없이 (Closed) 모든 구현을 선택 혹은 추가 (Open) 할 수 있다.
Strategy Pattern 구조
먼저 Strategy Pattern의 구조를 보면 다음과 같다.
ConcreteStrategy 1..n Class
- 알고리즘, 행위, 동작을 객체로 정의한 구현체
Strategy Interface
- 모든 전략 구현제에 대한 공용 인터페이스
Context Class
- 알고리즘을 실행해야 할 때마다 해당 알고리즘과 연결된 전략 객체의 메소드를 호출.
Context Class는 멤버변수로 Strategy Interface 객체를 가지고 있고,
Strategy에 대한 setter를 통해 구현체를 변경할 수 있다.
코드를 통해 살펴보면 다음과 같다.
// 모든 전략 구현제에 대한 공용 인터페이스
public interface Strategy {
void printSomething();
}
// 공용 인터페이스 구현체
public class ConcreteStrategy1 implements Strategy{
@Override
public void printSomething() {
System.out.println("ConcreteStrategy1 printSomething");
}
}
// 공용 인터페이스 구현체
public class ConcreteStrategy2 implements Strategy{
@Override
public void printSomething() {
System.out.println("ConcreteStrategy2 printSomething");
}
}
// 전략 인터페이스 실행 매개체
public class Context {
// 전략 인터페이스를 멤버변수로 선언
private Strategy strategy;
// 전략 인터페이스를 setter를 통해 변경
public void setStrategy(Strategy strategy) {
this.strategy = strategy;
}
// 전략 인터페이스의 메소드 실행
public void executeStrategy(){
this.strategy.printSomething();
}
}
public class Main {
public static void main(String[] args) {
// Context 생성
Context context = new Context();
// 전략 인터페이스 구현체 변경
context.setStrategy(new ConcreteStrategy1());
// 전략 인터페이스 메소드 실행
context.executeStrategy();
// 전략 인터페이스 구현체 변경
context.setStrategy(new ConcreteStrategy2());
// 전략 인터페이스 메소드 실행
context.executeStrategy();
}
}
Strategy Pattern 특징
먼저 전략 패턴이 필요한 이유에 대해 정리하면 다음과 같다.
서로 다른 여러 가지 알고리즘이 존재할 때, 전략 패턴을 사용하지 않는다면 중복되는 코드가 생기거나,
if - swtich 문을 통해 구현한다면, OCP 원리에 위배될 수 있다.
따라서 실행(런타임) 중에 알고리즘 전략을 선택하여,
객체 동작을 실시간으로 바꾸어 동작하도록 하는 것이 전략 패턴의 목적이다.
이에 따라 새로운 전략 (Strategy)를 생성해야 될 때,
이를 구현하는 구현체 ( ConcreteStrategy )만 생성하면,
다른 코드의 변경 없이 새로운 전략을 적용시킬 수 있다.
따라서 필요한 상황을 정리해 보면 다음과 같다.
- 전략 알고리즘의 여러 버전 또는 변형이 필요할 때
- 알고리즘 코드가 노출되어서는 안 되는 데이터에
액세스 하거나 데이터를 활용할 때 (캡슐화) - 알고리즘의 동작이 런타임에 실시간으로 교체되어야 할 때
주의점은 다음과 같다.
- 알고리즘이 많아질수록 관리해야 할 객체의 수가 늘어난다.
- 만일 어플리케이션 특성이 알고리즘이 많지 않고 자주 변경되지 않는다면,
새로운 클래스와 인터페이스를 만들어 프로그램을 복잡하게 만들 이유가 없다. - 개발자는 적절한 전략을 선택하기 위해 전략 간의 차이점을 파악하고 있어야 한다.
Strategy Pattern 적용 예시
public class Cafe {
private String type;
public void setType(String type){
this.type = type;
}
public void makeCoffee(){
switch (type) {
case "americano" -> System.out.println("Make Americano");
case "latte" -> System.out.println("Make Latte");
case "Cappuccino" -> System.out.println("Make Cappuccino");
}
}
}
public class Main {
public static void main(String[] args) {
Cafe cafe = new Cafe();
cafe.setType("americano");
cafe.makeCoffee();
cafe.setType("latte");
cafe.makeCoffee();
cafe.setType("Cappuccino");
cafe.makeCoffee();
}
}
위의 Cafe Class에서는 setType을 통해 type을 설정하고,
makeCoffee()에서는 멤버변수 type 값에 따라 일정한 로직을 실행하도록 구성되어 있다.
여기서 swtich문을 사용하는데, 커피의 종류가 늘어날 때마다
switch문을 수정해야 되고, 너무나 많은 case 구문이 나올 수 있다.
또한 OCP 원리에 위배된다. 이를 추상화 전략 인터페이스를 통해 변경하면 다음과 같다.
위의 OCP 원리를 위배하는 코드를 해결하는 가장 좋은 방법은
변경시키고자 하는 행위(전략)를 직접 넘겨주는 것이다.
우선 여러 커피들을 객체 구현체로 정의하고 이들을 Coffee이라는 인터페이스로 묶어준다.
그리고 Context Class 멤버변수로 Coffee 인터페이스 객체를 선언하고,
setCoffee() 메소드를 통해 전략 인터페이스 객체의 상태를 바로바로 변경할 수 있도록 구성해 준다.
public interface Coffee {
void makeCoffee();
}
public class Americano implements Coffee{
@Override
public void makeCoffee() {
System.out.println("Make Americano");
}
}
public class Latte implements Coffee{
@Override
public void makeCoffee() {
System.out.println("Make Latte");
}
}
public class Cappuccino implements Coffee{
@Override
public void makeCoffee() {
System.out.println("Make Cappuccino");
}
}
public class Context {
private Coffee coffee;
public void setCoffee(Coffee coffee) {
this.coffee = coffee;
}
public void takeCoffee(){
this.coffee.makeCoffee();
}
}
public class Main {
public static void main(String[] args) {
Context context = new Context();
context.setCoffee(new Americano());
context.takeCoffee();
context.setCoffee(new Latte());
context.takeCoffee();
context.setCoffee(new Cappuccino());
context.takeCoffee();
}
}
이를 통해 새로운 전략 객체 ( 새로운 종류의 커피 )가 필요하더라도
전략 인터페이스를 구현하는 구현체만 새로 생성한다면
기존의 코드 변경 없이 런타임 중에 해당 전략 알고리즘을 선택하여 사용할 수 있다.
이처럼 전략 패턴을 사용하면, OCP 원리에 부합하고,
객체지향의 핵심인 유지보수를 용이하게 할 수 있다.
따라서 상황에 따라 실시간으로 변경해야 하는 알고리즘이 많다면,
Strategy (전략) 패턴을 사용하는 게 유용할 수 있다.
참고자료
'IT > Java' 카테고리의 다른 글
[Java] Java Builder Pattern (2) | 2024.09.12 |
---|---|
[Java] Java Factory Pattern (1) | 2024.09.11 |
[Java] Java Decorator Pattern (0) | 2024.09.09 |
[Java] Java Observer Pattern (7) | 2024.09.08 |
[Java] Java 함수형 프로그래밍 (0) | 2024.08.27 |