스트래티지 패턴
스트래티지 패턴(전략 패턴)은 전략을 쉽게 바꿀 수 있도록 해주는 디자인 패턴이다. 여기서의 전략이란 어떤 목적을 달성하기 위해 작업을 수행하는 방식, 비즈니스 규칙, 문제를 해결하는 알고리즘 등을 말한다.
스트레티지 패턴은 같은 문제를 해결하는 여러 알고리즘이 클래스 별로 캡슐화 되어있고, 이를 필요할 때 교체할 수 있게 만들어 동일한 문제를 다른 알고리즘으로 해결할 수 있게 만든다.
스트래티지 패턴에서 나타나는 역할이 수행하는 작업은 다음과 같다.
•
Strategy : 인터페이스나 추상 클래스로 문제를 해결하는 방법(메서드 호출 방법)을 명시한다.
•
ConcreateStrategy1..3 : 인터페이스 혹은 추상 클레스의 해결 방법을 구현한다.
•
Context : 스트래티지 패턴을 이용한다. 동적으로 전략을 바꾸기 위해 setter 메서드를 제공하기도 한다.
스트래티지 패턴의 행위 모델을 순차 다이어그램으로 표현하면 위와 같다.
1.
클라이언트가 원하는 전략을 선택하고
2.
선택한 전략을 Context 객체에 바인딩 후
3.
선택한 전략에 따라 문제를 해결한다.
전략 패턴으로 로봇 만들기
상황
위의 클래스 다이어그램을 코드로 옮기면 다음과 같다.
public abstract class Robot {
private String name;
protected Robot(final String name) {
this.name = name;
}
protected String getName() {
return name;
}
public abstract void attack();
public abstract void move();
}
Java
복사
public class TaekwonV extends Robot {
public TaekwonV(final String name) {
super(name);
}
@Override
public void attack() {
System.out.println("TaekwonV attack");
}
@Override
public void move() {
System.out.println("walk");
}
}
Java
복사
public class Atom extends Robot {
public Atom(final String name) {
super(name);
}
@Override
public void attack() {
System.out.println("Atom attack");
}
@Override
public void move() {
System.out.println("fly");
}
}
Java
복사
public class Client {
public static void main(String[] args) {
final Robot taekwonV = new TaekwonV("TaekwonV");
taekwonV.attack();
taekwonV.move();
final Robot atom = new Atom("Atom");
atom.attack();
atom.move();
}
}
Java
복사
문제점
위 코드에서 기존의 공격 혹은 이동 방법을 추가하거나 변경하려고 하면,
public class Atom {
...
@Override
public void attak() {
System.out.println("new attack");
}
}
Java
복사
위와 같이 기존 코드에 변경이 발생하게 된다. 또한 이렇게 수정된 코드는 생성되는 모든 Atom 인스턴스의 공격 방식을 바꾸게 되므로 문제가 된다.
추가적으로 이와 같이 Sungard라는 새로운 로봇을 추가한다고 생각해보자. 새로 추가되는 Robot이 생길 때마다, 새로운 move()와 attack() 방식을 구현해주어야 한다. 심지어 기존의 다른 Robot에서 사용하는 move나 attck을 재활용하고 싶어도 할 수 없다.
해결책
이러한 문제를 해결하기 위해서는 설계에서 무엇이 변화되었는지를 찾고, 이를 캡슐화하여 스트래티지 패턴을 적용하면 해결할 수 있다.
위에서는 이동 기능과 공격 기능이 변한다는 것을 예시로 들었다. 따라서 이를 캡슐화하려면, 구체적인 이동 방식과 공격 방식을 담은 클래스들을 캡슐화해야한다.
이처럼 공격 방식과 이동 방식을 인터페이스로 캡슐화하고,
Robot 클래스 내에 인터페이스를 두어 변화에 쉽게 대처할 수 있도록 만들면 된다.
public abstract class Robot {
private String name;
private AttackStrategy attackStrategy;
private MovingStrategy movingStrategy;
protected Robot(final String name,
final AttackStrategy attackStrategy,
final MovingStrategy movingStrategy) {
this.name = name;
this.attackStrategy = attackStrategy;
this.movingStrategy = movingStrategy;
}
protected final String getName() {
return name;
}
public final void attack() {
attackStrategy.attack();
}
public final void move() {
movingStrategy.move();
}
}
Java
복사
public interface AttackStrategy {
void attack();
}
public class Missile implements AttackStrategy {
@Override
public void attack() {
System.out.println("missile attack");
}
}
public class Punch implements AttackStrategy {
@Override
public void attack() {
System.out.println("punch attack");
}
}
Java
복사
public interface MovingStrategy {
void move();
}
public class Walking implements MovingStrategy {
@Override
public void move() {
System.out.println("walking");
}
}
public class Fly implements MovingStrategy {
@Override
public void move() {
System.out.println("fly");
}
}
Java
복사
public class TaekwonV extends Robot {
public TaekwonV(final String name, final AttackStrategy attackStrategy, final MovingStrategy movingStrategy) {
super(name, attackStrategy, movingStrategy);
}
}
public class Atom extends Robot {
public Atom(final String name, final AttackStrategy attackStrategy, final MovingStrategy movingStrategy) {
super(name, attackStrategy, movingStrategy);
}
}
Java
복사
public static void main(String[] args) {
final Robot taekwonV = new TaekwonV("TaekwonV", new Missile(), new Walking());
taekwonV.attack();
taekwonV.move();
final Robot atom = new Atom("Atom", new Punch(), new Fly());
atom.attack();
atom.move();
}
Java
복사
전략 패턴 예시 2
public interface Analyzer {
boolean isSupport(final Version version);
// Version getVersion();
void analyze();
}
public class AnalyzerV2 implements Analyzer {
@Override
public boolean isSupport(final Version version) {
return version == Version.V2;
}
// @Override
// public Version getVersion() {
// return Version.V2;
// }
@Override
public void analyze() {
// V2 분석
}
}
public class AnalyzerV3 implements Analyzer {
@Override
public boolean isSupport(final Version version) {
return version == Version.V3;
}
// @Override
// public Version getVersion() {
// return Version.V3;
// }
@Override
public void analyze() {
// V3 분석
}
}
Java
복사
@RequiredArgsConstructor
public class AnalyzerFactory {
private List<Analyzer> analyzers;
// private Map<Version, Analyzer> analyzers;
public AnalyzerFactory(final List<Analyzer> analyzers) {
this.analyzers = analyzers;
// this.analyzers = analyzers.stream()
// .collect(Collectors.toMap(
// analyzer -> analyzer.getVersion(),
// analyzer -> analyzer
// ));
}
public Analyzer getAnalyzer(final Version version) {
return analyzers.stream()
.filter(analyzer -> analyzer.isSupport(version))
.findFirst()
.orElseThrow(() -> new RuntimeException("버전 없음"));
// return analyzers.get(version);
}
}
Java
복사
public enum Version {
V2, V3;
}
Java
복사