스트래티지 패턴(strategy pattern)

알고리즘군을 정의하고 각각캡슐화하여 교환해서 사용할 수 있도록 만든다.
스트래티지패턴을 활용하면 알고리즘을 사용하는 클라이언트와는 독립적으로 알고리즘을 변경할수 있다.

 

오리 어플리케이션 게임을 운영하는 회사를 다니면서 오리게임을 만든다고 가정했을때.

표준적인 객체지향 기법을 사용하여 Duck 이라는 슈퍼클래스를 만든다음 그 클래스를 확장하여 다른 종류의 오리를 만든다.

 

 

 

 

추상클래스인 Duck 클래스를 ReadHeadDuck 클래스와 MallardDuck 클래스가 상속을 받아 추상메소드인 display()를 각각 구현한다.

 

 

 

문제의 시작1

 

원래는 그럴 계획이 없었는데..

오리들이 물에 떠있는 기능 이외에 날아다녀야하는 요구사항이 생겼다.

간단하네.. ?

 

 

이제 모든 오리들에게 날수있는 기능이 추가되었다.

 

 

 

그런데 날 수없는 오리가 있었다는 사실을 잊고있었다.

 

 

Duck 코드의 한부분만을 바꿈으로 해서 프로그램 전체에 부작용이 발생하였다. (장난감 고무오리가 날아다님)

 

 

 

문제를 해결하기 위하여.

RubberDuck 클래스에서 fly() 메소드와 quack() 메소드를 오버라이드 하여 소리와 날수있게 하는기능을 변경시켜주었다.

 

 

일단 문제는 해결되었지만.. 향후에 RubberDuck과 같은 가짜오리가 더 추가가 된다면 그때마다 맞지않는 상속되는 메소드들을 오버라이드 해서 구현해야하는 문제가 여전히 존재한다.

 

 

문제의 시작2

 

회사에서 1개월마다 한번씩 새로운 오리를 업데이트 한다고 한다. 여러 오리가 새롭게 추가될것이고 그 규격도 계속 변할것이라고 한다.

그렇다면 매번 모든 오리 서브클래스의 fly() 와 quack() 같은 메소드를 일일이 살펴봐야하고 상황에따라 오버라이드로 해야할수있다.

상속활용이 맞는건가.. 다시생각해보자.

그렇다면 인터페이스를 사용한다면 ???

 

 

 

코드중복이 엄청나겠지..

메소드 몇게 오버라이드 해야하는것을 피하다가 날아가는 동작 바꾸기위해 새롭게 생긴 모든 Duck 서브클래스들을 전부 고쳐야하네.

 

 

 

 

 

해결해보자.

 

상속을 사용하는것도 서브클래스들의 행동이 바뀔수 있는데도 모든 서브클래스들이 하나의 행동을 사용하는것이 문제가되고

Flyable, Quackable 인터페이스 사용을 하는 방법도 코드재사용을 할수없다는 문제가 있다.

(한가지의 행동을 바꿀때마다 그 행동이 정의되어있는 모든 서브클래스들은 전부 찾아서 코드를 일일히 고쳐야 하고, 그 과정에서 새로운 버그가 생길 가능성이 많음!)

 

디자인 원칙

애플리케이션에서 달라지는 부분을 찾아내고, 달라지지 않는 부분으로부터 분리시킨다. 

 

달라지는 부분을 찾아서 나머지 코드에 영향을 주지 않도록 "캡술화"를 시켜준다.

 

오리마다 달라지는 부분은.. fly() 와 quack() 가 있네.

이러한 행동을 Duck 클래스로부터 분리시키기 위해 각 행동을 나타낼 새로운 클래스의 집합을 만들어 준다.

 

 

 

행동에 관한 인터페이스가 생기고 구체적인 행동을 구현하는 클래스들이 각각 생성이 된다.

이제 더이상 Duck에서 나는 행동과 소리를 내는 행동을 Duck 클래스나 그 서브클래스에서 구현하지않고 다른클래스에게 위임을 해주게 된다.

 

디자인 원칙
상속보다는 구성을 활용한다. 
 
디자인 원칙
구현이 아닌 인터페이스에 맞춰서 프로그래밍 한다. 

 

그리고 Duck 클래스는 두개의 인터페이스 형식의 인스턴스 변수가 추가가 된다.

 

 

Duck 클래스에서는 이제 행동을 직접 처리하는 대신 새로 만든 performQuck(), performFly() 메소드에서 각각 FlyBehavior, QuackBehavior 로 참보되는 객체에 그행동을 위임해 줄것이다.

 

public abstract class Duck {
     FlyBehavior flyBehavior;
     QuackBehavior quackBehavior;

     public void swim(){
        System.out.println("물에 떠있습니다.");
     }

     public abstract void display();

     public void performQuack(){
        quackBehavior.quack();
     }

     public void performFly(){
        flyBehavior.fly();
     }

     public void setFlyBehavior(FlyBehavior flyBehavior) {
         this.flyBehavior = flyBehavior;
     }

     public void setQuackBehavior(QuackBehavior quackBehavior) {
        this.quackBehavior = quackBehavior;
     }
 }

 

 public abstract class Duck {
    FlyBehavior flyBehavior;
    QuackBehavior quackBehavior;

     public void swim(){
     	System.out.println("물에 떠있습니다.");
     }

     public abstract void display();

     public void performQuack(){
     	quackBehavior.quack();
     }

     public void performFly(){
     	flyBehavior.fly();
     }

     public void setFlyBehavior(FlyBehavior flyBehavior) {
     	this.flyBehavior = flyBehavior;
     }

     public void setQuackBehavior(QuackBehavior quackBehavior) {
     	this.quackBehavior = quackBehavior;
     }
 }
 public interface FlyBehavior { 
      public void fly();
 }

 public class FlyWithWings implements FlyBehavior{
      @Override
      public void fly() {
          System.out.println("난다!!");
      }
 }

 public class FlyNoWay implements FlyBehavior{
      @Override
      public void fly(){
          System.out.println("날지못해요.");
      }
 }

 

public interface QuackBehavior { 
      public void quack();
}

public class Quack implements QuackBehavior {
      @Override
      public void quack() {
          System.out.println("꿱꿱.");
      }
 }

 public class Squack implements QuackBehavior {
      @Override
      public void quack(){
          System.out.println("삑삑.");
      }
 }

public class MuteQuack implements QuackBehavior {
      @Override
      public void quack(){
          System.out.prinln("조용.");
      }
 }
 public class MallardDuck() extends Duck {
     public MallardDuck(){
         flyBehavior = new FlyWithWings();
         quackBehavior = new Quack();
     }

     @Override
     public void display(){
         System.out.println("청둥오리 입니다.");
     }
 }
 public class MiniDuckSimulator{
     public static void main(String[] args){
         Duck mallard = new MallardDuck();
         mallard.performQuack();
         mallard.performFly();
         
         mallard.setFlyBehavior(new FlyNoWay());
         mallard.performFly();
     }
 }
 
 꿱꿱.
 난다!!
 날지못해요.

 

A는 B이다 보다 A에는 B가 있다가 나을수있다.

각각의 오리들에게는 FlyBehavio와 QuackBehavior이 있으며 각각 행동과 소리행동을 위임 받는다.

 

이런식으로 두클래스를 합치는 것을 구성(composition)을 이용하는 것이라고 한다.

여기의 어리 클래스는 행동을 상속 받는 대신, 올바른 행동 객체로 구성됨으로써 행동을 부여받게 된다.

 

 

참고.

  • Head First Design Patterns.
복사했습니다!