소프트웨어 관련/Design Pattern

[Design Pattern] Strategy Pattern

JJangGu 2022. 4. 3. 17:07

헤드퍼스트의 Design Patterns 의 내용을 보며 학습을 하고 기록을 남기려 합니다.

첫번째로는 Strategy Pattern 입니다.

 

Strategy Pattern

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

 

책에서는 Strategy Pattern을 마지막에 이렇게 정리를 하였습니다. 이제 책에 있는 예제를 활용하여 어떤 패턴인지 살펴보겠습니다. 

 

처음에 모든 오리들이 꽥꽥소리를 낼 수 있고, 헤엄칠 수 있기 때문에 Duck에 간단한 코드를 구현했습니다. MallaredDuck 과 RedheadDuck 외에도 다른 유형의 오리들도 Duck 클래스로 부터 상속을 받습니다.

 

이제 오리들이 날아다닐 수 있어야 하나고 요건이 추가됩니다. 그러면 위에서 했던 것과 마찬가지로 Duck 클래스에 fly() 메서드를 추가하겠죠.

Duck에 fly()를 추가했습니다. 그런데 여기서 문제가 생겼습니다. Duck 을 상속받은 RubberDuck 에도 fly가 추가되어 날 수 없는 고무오리가 날 수 있게되었죠. Duck 을 상속받은 서브클래스들에 적합하지 않은 행동들이 추가 될 수 있다는 것을 간과한 것입니다.

만약 여기서 구조를 수정하지 않고, RubberDuck이 나는 것을 원치 않는다면, fly() 메서드에 아무것도 하지않도록 Override 를 하면 되겠지만 불필요한 행동이죠.

 

여기서 이 문제를 수정하기 위한 첫번째 디자인 원칙이 들어갑니다.

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

 

Duck에서 fly 와 quack 은 오리마다 달라지는 부분입니다. 이 행동을 Duck 에서 빼내면서 각 행동을 나타낼 클래스 집합을 새로 만들겠습니다. 또 여기서 다른 디자인 원칙을 적용해보죠. 구현이 아닌 인터페이스에 맞춰서 프로그래밍한다. 

행동은 인터페이스로 표현하고, 행동을 구현할 때 인터페이스를 구현하는 것이죠.

 

이렇게 행동이 더 이상 Duck 클래스에 있지않고 두개의 인터페이스를 사용하면서 Duck 클래스를 건드리지 않고 행동을 추가할 수 있게 되었습니다. 또한 재사용할 수도 있죠.

 

이제 행동을 분리했으니 Duck 에 어떻게 적용하는지 봐야되겠네요. 중요한 것은 Duck 클래스에서 행동을 구현하는 것이 아니라 위임한다는 것입니다.

 

이런 형태가 만들어졌습니다. 그러면 이제는 실제 코드는 어떻게 구현되었는지 보겠습니다.

 

먼저, FlyBehavior 인터페이스와 구현 클래스입니다.

 

public interface FlyBehavior {
   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("날 수가 없어..");
   }
}

 

다음은 QuackBehavior 인터페이스와 구현체입니다.

 

public interface QuackBehavior {
   void quack();
}

 

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

 

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

 

public class MuteQuack implements QuackBehavior{
   @Override
   public void quack() {
      System.out.println("음소거..");
   }
}

 

이제 Duck 클래스입니다.

 

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("모든 오리는 수영할 수 있습니다.");
   }
}

 

이제 위의 코드를 바탕으로 테스트를 해보겠습니다.

 

public class DuckSimulator {
   public static void main(String[] args) {
      Duck mallard = new MallardDuck();
      mallard.display();
      mallard.performFly();
      mallard.performQuack();

      Duck readHead = new RedheadDuck();
      readHead.display();
      readHead.performFly();
      readHead.performQuack();

      Duck rubberDuck = new RubberDuck();
      rubberDuck.display();
      rubberDuck.performFly();
      rubberDuck.performQuack();
   }
}

 

테스트 결과입니다. 

 

흔히 상속을 배울때 "A는 B이다" 라는 is - a 관계로 표현을 합니다. 그런데 여기서 또 다른 디자인 원칙을 생각해봅시다.

지금 예제를 통해 구현된 방식은 "A는 B이다" 가 아니라 "A에는 B가 있다" 입니다. 

각 오리에는 FlyBehavior 와 QuackBehavior 가 있으며, 각각 나는 행동과 꽥꽥거리는 행동을 위임받습니다.

 

두 클래스를 이런식으로 합치는 것을 Composition(구성)이라고 합니다. 여기서의 디자인 원칙은 상속보다는 구성을 활용한다 입니다.

 

Strategy Pattern 은 행위를 캡슐화해 동적으로 행위를 자유롭게 바꿀 수 있게 해주는 패턴이며, 같은 문제를 해결하는 여러 알고리즘이 캡슐화되어 있고 이들이 필요할 때 교체할 수 있도록 함으로써 동일한 문제를 다른 알고리즘으로 해결할 수 있게 하는 디자인 패턴입니다.