[디자인 패턴] 전략 패턴(Strategy Pattern) 구현하기
전략 패턴
전략 패턴은 전략에 일을 수행하는 방법이 여러가지일 때, 각 방법들을 추상화/캡슐화하고 로직을 사용하는 곳에서는 추상된 인터페이스만 사용함으로써 코드는 바뀌지 않지만 사용하는 곳마다 알고리즘은 바뀔 수 있게끔 하는 패턴이다.
예시로는 java 에서 자주 보이는 comparator를 들 수 있다.
- comparator 인터페이스
- sort 시 비교하는 방법이 여러가지가 될 수 있으므로, comparator 가 직접 수행하는 로직은 추상화/캡슐화하고 사용하는 곳에서 추상된 인터페이스를 사용하면서 로직을 부분적으로 변경하는 방식
(ex. a 와 b를 비교 시, a 가 더 크다고 하고 싶다.)- 이 인터페이스를 사용해서 원하는 비교 로직을 만들 수 있도록 하는 것이 concrete 한 stratagy를 만들어주는 과정이 된다.
- sort 시 비교하는 방법이 여러가지가 될 수 있으므로, comparator 가 직접 수행하는 로직은 추상화/캡슐화하고 사용하는 곳에서 추상된 인터페이스를 사용하면서 로직을 부분적으로 변경하는 방식
자세히 알아보도록 하자. :)
전략 패턴은 여러 알고리즘들을 캡슐화하고 상호 교환이 가능하게 만드는 패턴이다.
로직들 중, 상황에 따라 달라지는 전략을 strategy라는 interface로 만들고 Context는 strategy 인터페이스를 참조한다. Concrete Strategy는 구현체로 각 strategy를 구현하고 있으며, 상호 교환 가능하여 client는 여러 전략들을 바꿔 껴가면서 사용할 수 있다.
전략 패턴의 필요성
만일, 무궁화꽃이 피었습니다를 하는 메서드를 만든다고 가정하자.
아래처럼, client 는 BlueLightRedLight 이라는 객체를 만들어서 Speed를 넘기고 blue/redLight 메서드를 불러서 이를 동작시킨다.
public class BlueLightRedLight {
private int speed;
public BlueLightRedLight(int speed) {
this.speed = speed;
}
public void blueLight() {
if (speed == 1) {
System.out.println("무 궁 화 꽃 이");
} else if (speed == 2) {
System.out.println("무궁화꽃이");
} else {
System.out.println("무광꼬치");
}
}
public void redLight() {
if (speed == 1) {
System.out.println("피 었 습 니 다.");
} else if (speed == 2) {
System.out.println("피었습니다.");
} else {
System.out.println("피어씀다");
}
}
}
그러나, 위의 방법의 경우 새로운 스피드가 생길 경우 Context에 해당하는 것과 전략들이 섞여있기 때문에, 나머지 speed 들의 코드들도 영향을 받게되어 단일 책임 정책을 위배한다.
전략 패턴을 사용하여 이를 개선해보자.
전략 패턴의 기본 구현
SpeedStrategy 인터페이스는 Strategy의 역할로 이를 구현하는 ConcreteStrategy들은 해당 전략을 보유하고 이를 구현하게된다.
package com.gaegul.strategy;
public interface SpeedStrategy {
void blueLight();
void redLight();
}
Context는 아래와 같이 구현한다.
package com.gaegul.strategy;
public class BlueLightRedLight {
//전략을 갖고 일을 한다.
private SpeedStrategy speed;
public BlueLightRedLight(SpeedStrategy speed) {
this.speed = speed;
}
public void blueLight() {
speed.blueLight();
}
public void redLight() {
speed.redLight();
}
}
SlowSpeed, NormalSpeed 라는 ConcreteStrategy들을 추가하자.
package com.gaegul.strategy;
public class SlowSpeed implements SpeedStrategy {
@Override
public void blueLight() {
System.out.println("무 궁 화 꽃 이");
}
@Override
public void redLight() {
System.out.println("피 었 습 니 다.");
}
}
public class NormalSpeed implements SpeedStrategy{
@Override
public void blueLight() {
System.out.println("무궁화꽃이");
}
@Override
public void redLight() {
System.out.println("피었습니다.");
}
}
이렇게 구현한 경우 Client 는 아래처럼 사용하게된다.
package com.gaegul.strategy;
public class Client {
public static void main(String[] args) {
BlueLightRedLight blueLightRedLight = new BlueLightRedLight(new SlowSpeed());
blueLightRedLight.blueLight();
blueLightRedLight.redLight();
}
}
이전의 방식의 경우 FastSpeed라는 속도를 추가하기 위해서는 BlueLightRedLight 코드에서 수정해야하므로 다른 속도들에도 영향이 갔다. 그러나, 위의 방법은 아래처럼 FastSpeed라는 ConcreteStrategy만 추가해주고 Client에서 해당 Speed 를 생성자로 넘겨 실행하기만 하면된다.
package com.gaegul.strategy;
public class FastSpeed implements SpeedStrategy{
@Override
public void blueLight() {
System.out.println("무광꼬치");
}
@Override
public void redLight() {
System.out.println("피어씀다!");
}
}
또한, Context쪽에서 operation 에 ConcreteStrategy를 넘겨받는 방식을 사용한다면 아래처럼 blue/red 각각 다른 speed 를 적용 가능하며 client에서 직접 동적으로 strategy를 구현할 수도 있다. (해당 방법은 위에서 언급한 comparator 의 구현 방식과 유사하다.)
package com.gaegul.strategy;
public class Client {
public static void main(String[] args) {
BlueLightRedLight blueLightRedLight = new BlueLightRedLight();
blueLightRedLight.blueLight(new SlowSpeed());
blueLightRedLight.redLight(new SpeedStrategy() {
@Override
public void blueLight() {
System.out.println("무꽃");
}
@Override
public void redLight() {
System.out.println("폈!");
}
});
}
}
전략 패턴의 장/단점
장점
- 새로운 전략을 추가해도 기존 코드를 변경하지 않는다.
- 상속 대신 위임을 사용할 수 있다.
- 상속은 하나밖에 상속이 안되기에, 정작 상속이 필요한 경우에 못 사용할 수 있고 하위 클래스들이 전부 영향을 받게된다. 따라서, Strategy를 받아서 위임하는 방식을 취하면 영향을 끊어낼 수 있다.
- 런타임에 전략을 변경할 수 있다.
단점
- 복잡도가 증가한다.
- 클라이언트 코드가 구체적인 전략을 알아야한다.