전략 패턴은 알고리즘군을 정의하고 캡슐화해서 각각의 알고리즘군을 수정해서 쓸 수 있게 해 줍니다.
전략 패턴을 사용하는 경우 / 사용해야 하는 이유
abstract class Duck {
// 일반 메서드 quack
public void quack() {
System.out.println("Quack! Quack!");
}
// 일반 메서드 swim
public void swim() {
System.out.println("Swimming gracefully.");
}
// 추상 메서드 display
public abstract void display();
}
클래스 설명을 하자면, Duck이라는 슈퍼 클래스에서 quack 및 swim 메서드가 정의되어 있고, display 메서드는 추상 메서드로 정의되어 있어 자식 클래스들이 이 메서드를 상속받아야 하며 구현해야 합니다.
청둥오리, 빨간 머리 오리 등 다양한 오리 클래스들이 Duck을 상속받고 있습니다.
지금까지는 별 문제없어 보이지만 새로운 기능을 추가시켰을 때 문제가 발생했습니다..
책에서는 임원진들이 새로운 기능을 넣으라고 하죠. 이처럼 개발자들에게 기능 추가는 숙명입니다...
Duck이라는 클래스에 fly라는 메서드가 새로 들어왔습니다. 코드로 예시를 들어보겠습니다.
abstract class Duck {
// 일반 메서드 quack
public void quack() {
System.out.println("Quack! Quack!");
}
// 일반 메서드 swim
public void swim() {
System.out.println("Swimming gracefully.");
}
// 일반 메서드 fly
public void fly()
System.out.println("I can fly!!");
}
// 추상 메서드 display
public abstract void display();
}
임원진의 명령대로 새가 날 수 있다는 기능을 추가하였습니다. 별 문제가 없어 보이죠..?!
문제가 생겼습니다.
러버덕이 Duck 클래스를 상속받았는데 부모 클래스의 fly메서드가 러버덕 클래스에 추가된 거죠
러버덕을 날지 못하는데 fly라는 메서드를 받았으니 정말 큰 문제가 생겼습니다.
그러면 날지 못하는 새와 나는 새를 구분 한 뒤, 메서드를 따로 주면 되는 거 아니야?
이런 생각을 한 여러분에게는 전략 패턴이 필요합니다.
바뀌는 부분과 바뀌지 않는 부분 분리하기
바뀌는 부분과 바뀌지 않는 부분을 분리하려면 여러분은 도메인에 대해 완벽히 이해를 하셔야 합니다.
오리 클래스에서는 fly를 제외하고도 quack메서드가 서브클래스에 따라 변화합니다.
- 고무오리의 울음소리와 청둥오리의 울음소리는 다르기 때문
이렇게 바뀌는 부분을 분리한 후에는 인터페이스로 바뀌는 부분을 정의한 후, 세부 사항은 인터페이스의 구현체로 구현하면 됩니다.
다이어그램을 간단히 설명하자면 FlyBehavior 인터페이스의 fly 메서드를 인터페이스의 구현체인 FlyWithWings와 FlyNoWay가 구현하는 것이다. (Quack도 똑같은 원리다)
전략 패턴의 장점
public abstract class Duck {
FlyBehavior flyBehavior;
QuackBehavior quackBehavior;
public Duck() {}
public abstract void display();
public void performFly() {
flyBehavior.fly();
}
public void performQuack() {
quackBehavior.quack();
}
public void swim() {
System.out.println("모든 오리는 물에 떠요.");
}
}
새롭게 변경된 Duck 클래스입니다.
FlyBehavior, QuackBehavior이 인스턴스 변수로 선언되었으며, performFly, performQuack이라는 메서드가 생겼습니다. 이 performFly, performQuack을 살펴보면 fly, quack을 직접 실행시키는 것이 아닌 메서드를 통해 실행시킴을 볼 수 있습니다. (다음 코드를 보면서 추가설명 하겠습니다)
public class MallardDuck extends Duck {
public MallardDuck() {
quackBehavior = new Quack();
flyBehavior = new FlyWithWings();
}
@Override
public void display() {
System.out.println("청둥오리에요");
}
}
Duck을 상속한 MallarDuck 클래스입니다.
MallarDuck 클래스를 보면 생성자에서 new 연산자를 통해 구현체를 생성한 뒤, quackBehavoir, flyBehavior에 할당하고 있음을 볼 수 있습니다.
이렇게 구현을 하면 다형성을 이용하여 동적으로 런타임 도중에 알고리즘을 선택할 수 있다는 장점이 있습니다.
Set을 이용해서 동적으로 행동 지정하기
public void setFlyBehavior(FlyBehavior fb) {
flyBehavior = fb;
}
public void setQuackBehavior(QuackBehavior qb) {
quackBehavior = qb;
}
set메서드를 이용하면 언제든지 오리의 행동을 동적으로 즉석에서 바꿀 수 있다는 장점이 있습니다.
코드와 함께 설명하겠습니다.
- Duck의 서브 클래스(ModelDuck) 생성
- flyBehavior의 구현체(FlyRocketpowered) 생성
- 테스트 클래스 생성
public class ModelDuck extends Duck {
public ModelDuck() {
flyBehavior = new FlyNoWay();
quackBehavior = new Quack();
}
@Override
public void display() {
System.out.println("저는 모형 오리에요.");
}
}
public class FlyRocketPowered implements FlyBehavior{
public void fly() {
System.out.println("로켓 추진으로 날아가요.");
}
}
public class MiniDuckSimulator {
public static void main(String[] args) {
Duck model = new ModelDuck();
model.performFly();
model.setFlyBehavior(new FlyRocketPowered());
model.performFly();
}
}
참고해야 하는 코드
1. performFly (Duck 클래스를 보셔도 됩니다)
public void performFly() { // duck 클래스의 performFly 메서드
flyBehavior.fly();
}
2. FlyNoWay
public class FlyNoWay implements FlyBehavior{
@Override
public void fly() {
System.out.println("저는 못 날아요");
}
}
테스트 클래스를 설명하자면,
- 다형성을 이용해 ModelDuck을 Duck에 할당하였습니다.
- performFly 메서드를 통해 행동 클래스에 책임을 위임합니다.
- set 메서드를 이용해서 flyBehavior에 FlyRocketPowered를 할당합니다.
- performFly 메서드를 통해 행동 클래스에 책임을 위임합니다.
출력값
저는 못 날아요
로켓 추진으로 날아가요.
Set 메서드를 통해 flyBehavior에 새로운 객체를 할당한 모습을 출력값을 통해 확인할 수 있습니다.