Notice
Recent Posts
Recent Comments
Link
«   2025/12   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

기록하자..

전략 패턴(Strategy Pattern) : Head First Design Pattern 본문

디자인패턴공부

전략 패턴(Strategy Pattern) : Head First Design Pattern

P23Yong 2021. 10. 19. 16:36

Strategy Pattern

전략 패턴에서는 알고리즘군을 정의하고 각각을 캡슐화하여 교환해서 사용할 수 있도록 만든다.

전략을 활용하면 알고리즘을 사용하는 클라이언트와는 독립적으로 알고리즘을 변경할 수 있다.


/** Head First Design에서의 문제를 가져왔다. **/

문제의 시작

/** 기존 오리 시뮬레이션 프로그램 **/

오리 시뮬레이션 프로그램

 

오리 시뮬레이션 프로그램을 개선하기 위해 fly 기능을 추가하고 싶다고 한다.

어떻게 할 수 있을까?

 

방법 1. 상속의 활용

만약 Duck을 상속받는 RubberDuck이 존재한다면 어떻게 해야할까?

상속의 문제점은 자식은 취사 선택을 할 수 없다는 것이다.

부모에 정의된 모든 메서드가 모든 자식에 적합하지 않을 수가 있다.

 

이를 해결하기 위해서는

quack()메서드는 "삑삑" 소리를 내도록 오버라이드해야하고 fly()메서드는 빈 메서드로 오버라이드 해야한다.

 

방법 2. 인터페이스의 활용

날수 있는 오리들과 꽥꽥 거릴 수 있는 오리들을 구분하기 위해 Flyable 인터페이스와 Quackable 인터페이스를 사용

interface를 이용하면 특정 기능을 제공하는 것과 그렇지 않은 것들을 구분할 수는 있다. 하지만 다양한 조합만큼 interface의 수가 늘어나고 이는 너무 비효율적이다. 또한 fly나 quack을 바꾸기 위해서는 Duck의 서브 클래스들을 일일히 바꾸어주어야 한다.

 


문제를 파악

관련 디자인 원칙 1

위의 조합 문제의 해결책으로는 상속이 좋은 해결책이 되지 못한다는 것을 알았다. 또한 interface 기능도 좋은 해결책이 아닌 것도 알았다.

그렇다면 어떻게 해결할 수 있을까?  -> 포함관계 이용, 관계 주입

여기서 사용할 수 있는 디자인 원칙이 있다.

 

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

 

이는 바뀌는 부분을 캡슐화시키라는 말과 같다.

 

관련 디자인 원칙 2

구현이 아닌 인터페이스에 맞춰서 프로그래밍 한다.

 

그렇다면 오리시뮬레이션 프로그램에서 달라지는 부분은 무엇일까? 

fly()와 quack()은 Duck클래스에서 오리마다 달라지는 부분이다.

 

이 두 행동을 Duck클래스에서 끄집어내어 각 행동을 나타낼 클래스 집합으로 만든다.

클래스 집합은 유연하게 만들고 Duck의 인스턴스에 행동을 할당할 수 있어야 한다.

 

이렇게 한다면 코드 재사용이 가능하고 기존 클래스를 변경하지 않고 새로운 행위를 추가하기 쉽다.

 

코드로 살펴보자.

public abstract class Duck {
    protected FlyBehavior flyBehavior;
    protected QuackBehavior quackBehavior;
    
    public Duck() {}
    
    public abstract void display();
    
    publick void fly() {
        flyBehavior.fly();
    }
    public void quack() {
        quackBehavior.quack();
    }
    
    public void setFlyBehavior(FlyBehavior fb) {
        flyBehavior = fb;
    }
    public void setQuackBehavior(QuackBehavior qb) {
        quackBehavior = qb;
    }
}
public class ModelDuck extends Duck {
    public ModelDuck() {
        flyBehavior = new FlyNoWay();
        quackBehavior = new Quack();
    }
    
    public void display() {
        System.out.println("I'm Model Duck!");
    }
}

 

이렇게 한다면 flyBehavior를 실행시간에 변경시킬 수 있을 뿐만아니라

Duck model = new ModelDuck();
model.setFlyBehavior(new FlyNoWay());
model.fly();

새로운 오리 타입을 만들기도 쉽다.

public class MallardDuck extends Duck {
    public MallardDuck() {
        flyBehavior = new FlyWithWings();
        quackBehavior = new Quack();
    }
    public void display() {
        System.out.println("I'm Mallard Duck!");
    }
}

Has-a는 Is-a보다 나을 수 있다.

상속보다는 구성을 활용하라

 

is-a관계를 사용하는 것보다 has-a관계를 사용하는 것이 보다 유연한 해결책을 가져다 준다.

is-a관계는 클래스간 관계가 정적 관계라 실행시 바꿀 수 없지만 has-a관계는 그렇지 않기 때문에 실행시간에 행위를 변경할 수 있다. (추상타입 & 관계주입 사용)

 


Strategy Pattern 정리

 

장점

  - 위임이라는 느슨한 연결을 통해 전략(strategy)를 쉽게 바꿀 수 있고 실행시간에 변경할 수도 있다.(has-a, DI 사용)

  - 여러 행위가 전략 패턴으로 구성될 경우 이들의 조합으로 객체를 쉽게 구성할 수 있다.

단점

  - 행위의 모델링이 쉽지 않다. 특히 전략이 클라이언트의 상태가 필요할 경우 효과적으로 이것을 설계하는 것이 어려울

    수 있다.

 

패턴의 변형

클라이언트가 전략을 필요로 하는 것이 아닌 전략이 클라이언트의 상태가 필요한 경우

 

클라이언트의 상태를 전략에게 전달할 수 있어야하는데 이때 방법이 2가지가 있다.

  • 방법 1: 클라이언트 자체를 전달 (위의 예제에서 오리 자체를 전달)
  • 방법 2: 필요한 데이터만 전달 (위의 예제에서 오리의 날개 정보만 전달)

클라이언트 자체를 전달하는 것은 결합성이 높아지는 단점과 다른 종류의 클라이언트에서 해당 전략을 사용하는 것이 어려울 수 있다.

전략을 활용하는 클라이언트가 한 종류이고, 전략마다 활용하는 클라이언트의 상태가 다르면 이 방법이 방법2보다는 효과적일 수 있다.
방법 2는 여러 전략에서 서로 다른 데이터가 필요하면 이를 처리하기 쉽지 않다.

 

 

'디자인패턴공부' 카테고리의 다른 글

명령 패턴(Command Pattern)  (0) 2021.11.28
생성 패턴(Factory Pattern)  (0) 2021.11.28
Decorator 패턴  (0) 2021.10.26
관찰자 패턴(Observer Pattern)  (0) 2021.10.19
SOLID  (2) 2021.10.15