정의
객체들이 할 수 있는 행위 각각에 대해 전략 클래스를 생성하고, 유사한 행위들을 캡슐화 하는 인터페이스를 정의하여, 객체의 행위를 동적으로 바꾸고 싶은 경우 직접 행위를 수정하지 않고 전략을 바꿔주기만 함으로써 행위를 유연하게 확장하는 방법이다.
간단히 말해서 객체가 할 수 있는 행위들 각각을 전략으로 만들어 놓고, 동적으로 행위의 수정이 필요한 경우 전략을 바꾸는 것만으로 행위의 수정이 가능하도록 만든 패턴이다.
먼저, 전략패턴을 적용하지 않은 예를 보도록 하겠습니다.
위와 같은 관계의 클래스 구조가 있습니다.
Movable.java
public interface Movable {
public void move();
}
public class Train implements Movable {
public void move(){
System.out.println("선로를 통해 이동");
}
}
public class Bus implements Movable {
public void move(){
System.out.println("도로를 통해 이동");
}
}
해당 운송 수단을 사용하는 Client 는 아래와 같습니다.
Client.java
public class Client {
public static void main(String args[]){
Movable train = new Train();
Movable bus = new Bus();
train.move();
bus.move();
}
}
위 코드를 보면, 기차는 선로를 따라 이동하고, 버스는 도로를 따라 이동합니다.
그러다 시간이 흘러 선로를 따라 움직이는 버스가 개발되었다고 가정해봅시다.
그러면 Bus의 move() 메서드를 다음과 같이 바꿔주기만 하면 끝납니다.
public void move(){
System.out.println("선로를 따라 이동");
}
그런데, 이렇게 수정하는 방식은 SOLID 원칙 중 OCP(Open-Closed Principle)에 위배됩니다.
OCP : 소프트웨어 개체는 확장에 대해 열려있어야 하고, 수정에 대해서는 닫혀 있어야 한다.
또한 지금과 같은 방식의 변경은 시스템이 확장이 되었을 때 유지보수를 어렵게 합니다.
예를 들어, 버스와 같이 도로를 따라 움직이는 택시, 자가용, 고속버스 등이 추가된다고 할 때, 모두 버스와 같이 move() 메서드를 사용합니다.
만약에 새로 개발된 선로를 따라 움직이는 택시, 자가용, 고속버스 등이 생긴다면,
택시, 자가용, 고속버스의 move() 메서드를 하나씩 수정해야 하며, 같은 메서드를 여러 클래스에서 똑같이 정의하고 있으므로 메서드의 중복이 발생합니다.
위와 같은 문제를 해결하기 위해 Strategy Pattern을 사용할 수 있습니다.
도로 또는 선로를 따라 움직이는 운송수단 (버스, 기차, 택시, ...) 이 있습니다.
해당 상황에서 시스템이 유연하게 변경되고 확장 될 수 있도록 전략 패턴을 적용해보겠습니다.
Strategy 생성
먼저 전략을 생성하는 방법입니다.
현재 운송수단은 선로를 따라 움직이든지, 도로를 따라 움직이든지 두 가지 방식이 있습니다.
즉, 움직이는 방식에 대해 Strategy 클래스를 생성하도록 합니다. (RailLoadStrategy, LoadStrategy)
그리고 두 클래스는 move() 메서드를 구현하여, 어떤 경로로 움직이는지에 대해 구현합니다.
또한 두 전략 클래스를 캡슐화 하기 위해 MovableStrategy 인터페이스를 생성합니다.
MovableStrategy.java
public interface MovableStrategy {
public void move();
}
public class RailLoadStrategy implements MovableStrategy{
public void move(){
System.out.println("선로를 통해 이동");
}
}
public class LoadStrategy implements MovableStrategy{
public void move(){
System.out.println("도로를 통해 이동");
}
}
운송수단 정의
다음으로 운송 수단에 대한 클래스를 정의할 차례입니다.
기차와 버스 같은 운송 수단은 move() 메서드를 통해 움직일 수 있습니다.
그런데 이동방식을 직접 메서드로 구현하지 않고, 어떻게 움직일 것인지에 대한 전략을 설정하여, 그 전략의 움직임 방식(move() 메서드)을 사용하여 움직이도록 합니다.
그래서 전략을 설정하는 메서드인 setMovableStrategy()가 존재합니다.
Moving 클래스는 무분별한 상속(is-a)을 피하기 위해 MovableStrategy를 상속하지 않고, Composite(has-a)을 사용합니다.
public class Moving{
private MovableStrategy movableStrategy;
public void move(){
movableStrategy.move();
}
public void setMovableStrategy(MovableStrategy movableStrategy){
this.movableStrategy = movableStrategy;
}
}
// Moving 객체를 상속(is-a)받은 Bus Class
public class Bus extends Moving{
}
// Moving 객체를 상속(is-a)받은 Train Class
public class Train extends Moving{
}
Client 구현
이제 Train 과 Bus 객체를 사용하는 Client를 구현할 차례입니다.
Train과 Bus 객체를 생성한 후에, 각 운송 수단이 어떤 방식으로 움직이는지 설정하기 위해 setMovableStrategy() 메서드를 호출합니다.
그리고 전략 패턴을 사용하면 프로그램 상으로 로직이 변경 되었을 때, 얼마나 유연하게 수정을 할 수 있는지 살펴보기 위해 선로를 따라 움직이는 버스가 개발되었다는 상황을 만들어 버스의 이동 방식 전략을 수정했습니다.
public class Client{
public static void main(String args[]){
Moving train = new Train();
Moving bus = new Bus();
/**
* 기존의 기차와 버스의 이동 방식
* 1) 기차 - 선로
* 2) 버스 - 도로
*/
train.setMovableStrategy(new RailLoadStrategy());
bus.setMovableStrategy(new LoadStrategy());
train.move();
bus.move();
/**
* 선로를 따라 움직이는 버스가 개발
*/
bus.setMovableStrategy(new RailLoadStrategy());
bus.move();
}
}