알고리즘 군을 정의하고(추상 클래스) 같은 알고리즘을 각각 하나의 클래스로 캡슐화한 다음, 필요할 때 서로 교환해서 사용할 수 있게 하는 패턴으로, 행위를 클래스로 캡슐화해 동적으로 행위를 자유롭게 바꿀 수 있게 해주는 패턴이다.
즉, 어떤 일을 수행하는 알고리즘이 여러가지 일때, 동작들을 미리 전략으로 정의함으로써 런타임중에 손쉽게 전략을 교체할 수 있는, 알고리즘 변형이 빈번하게 필요한 경우에 적합한 패턴이다.
- Strategy(전략 인터페이스) : 모든 전략 구현제에 대한 공용 인터페이스
- ConcreteStrategy(전략 알고리즘 객체) : 알고리즘, 행위, 동작을 객체로 정의한 구현체
- Context(컨텍스트) : 알고리즘을 실행해야 할 때마다 해당 알고리즘과 연결된 전략 객체의 메소드를 호출.
// MoveStrategy.java (Strategy)
public interface MoveStrategy {
void move();
}
// Run.java (ConcreteStrategy)
public class Run implements MoveStrategy {
@Override
public void move() {
System.out.println("[Run] Robot is running...");
}
}
// Walk.java (ConcreteStrategy)
public class Walk implements MoveStrategy {
@Override
public void move() {
System.out.println("[Walk] Robot is walking...");
}
}
// Robot.java (Context)
public class Robot {
private MoveStrategy moveStrategy;
public Robot(MoveStrategy moveStrategy) {
this.moveStrategy = moveStrategy;
}
public void setMoveStrategy(MoveStrategy moveStrategy) {
this.moveStrategy = moveStrategy;
}
public void move() {
moveStrategy.move();
}
}
// Client.java (Client)
public class Client {
public static void main(String[] args) {
Robot robot = new Robot(new Run());
robot.move();
robot.setMoveStrategy(new Walk());
robot.move();
}
}
// 실행 결과
[Run] Robot is running...
[Walk] Robot is walking...
위 예제에서 로봇이라는 객체의 이동방식을 MoveStrategy라는 인터페이스의 move 메소드를 사용해 정의하고 하위에 Run과 Walk라는 클래스에 move 메소드를 각각 구현했다. 즉, 이동이라는 행위를 클래스로 구현하고 Robot의 속성으로서 가지고 있다가 이동 시에 move 메소드가 실행이 되는 것이다. 전략 패턴의 장점은 확장에도 용이하다는 것이다. 만약 여기서 로봇이 말을 할 수 있다면, 그리고 한국어와 영어를 구사할 수 있다면 아래와 같이 구현할 수 있다.
// LanguageStrategy.java (Strategy)
public interface LanguageStrategy {
void speak();
}
// Korean.java (ConcreteStrategy)
public class Korean implements LanguageStrategy {
@Override
public void speak() {
System.out.println("[Korean] Robot speaks in Korean...");
}
}
// English.java (ConcreteStrategy)
public class English implements LanguageStrategy {
@Override
public void speak() {
System.out.println("[English] Robot speaks in English...");
}
}
// Robot.java (Context)
public class Robot {
private MoveStrategy moveStrategy;
private LanguageStrategy languageStrategy;
public Robot(MoveStrategy moveStrategy, LanguageStrategy languageStrategy) {
this.moveStrategy = moveStrategy;
this.languageStrategy = languageStrategy;
}
public void setMoveStrategy(MoveStrategy moveStrategy) {
this.moveStrategy = moveStrategy;
}
public void move() {
moveStrategy.move();
}
public void setLanguageStrategy(LanguageStrategy languageStrategy) {
this.languageStrategy = languageStrategy;
}
public void speak() {
languageStrategy.speak();
}
}
// Client.java (Client)
public class Client {
public static void main(String[] args) {
Robot robot = new Robot(new Run(), new Korean());
robot.move();
robot.speak();
robot.setMoveStrategy(new Walk());
robot.move();
robot.setLanguageStrategy(new English());
robot.speak();
}
}
// 실행 결과
[Run] Robot is running...
[Korean] Robot speaks in Korean...
[Walk] Robot is walking...
[English] Robot speaks in English...
위의 언어설정과 말하기 기능이 구현된 클래스들을 생성한 후 Robot 클래스에 언어설정 속성을 추가해 준다. 이렇게 간단하게 새로운 기능을 추가할 수 있다. 만약 언어의 종류나 이동의 종류가 많아지는 경우에는 그저 Strategy를 implement하는 구현 클래스만 추가해주면 되기 때문에 매우 간편하다.
만약 전략 패턴을 사용하지 않고 위와 동일한 모듈을 구현한 후 새로운 기능을 추가하려고 한다면 예컨데 Robot을 인터페이스로 구현한 후 해당 인터페이스를 상속받는 WalkingRobot과 RunningRobot으로 구현했을 것이고, 거기에 말하기 기능을 추가하려면 KoreanWalkingRobot, KoreanRunningRobot, EnglishWalkingRobot, EnglishRunningRobot 으로 구현해야 했을 것이다. 이 방식은 기능이 추가될수록 구현해야 하는 클래스가 기하급수적으로 많아지고, 변경 시 기존 클래스를 수정해야 한다는 점에서 객체지향 설계원칙의 OCP(개방 폐쇄의 원칙)에 어긋난다.