본문 바로가기

- Programming/- 디자인 패턴

★ 3. 스트래티지 패턴 (Strategy Pattern)

반응형

스트래티지 패턴 (Strategy Pattern)


스트래티지 패턴에서는 알고리즘 군을 정의하고 각각을 캡슐화하여 교환해서 사용할 수 있도록 만듭니다.

스트래티지를 활용하면 알고리즘을 사용하는 클라이언트와는 독립적으로 알고리즘을 변경할 수 있습니다.


부모 - 자식간의 상속을 이용해 동물의 행동을 호출하는 간단한 시스템에서

스트래티지 패턴을 적용해보면서 설명하도록 하겠습니다.




위 그림을 살펴보겠습니다.
Animal 클래스를 만든 뒤 동물 공통의 행동 함수인 울음(Cry), 움직임(Move), 동물의 모습을 표현하는
display 메소드를 정의했습니다.
여기서 구현할 동물은 Animal 클래스를 상속 받는 독수리(Eagle)와 호랑이(Tiger)입니다.
독수리와 호랑이는 각각의 울음(Cry)과 모습(display) 메소드를 구현했습니다.

일반적인 상속을 이용한 시스템입니다.
이러한 시스템으로 우리는 Eagle, Tiger의 객체를 생성하여 독수리의 울음 소리와 움직임, 그리고 모습을 호출할 수 있습니다.
Eagle, Tiger가 아닌 새로운 동물이 추가된다면 새로운 동물의 객체를 만들고 Animal 클래스를 상속 받아 그 동물의 필요한
기능(cry, move, display)을 구현해주면 됩니다.

여기서 이제 동물의 기능을 더 추가하려면 어떻게 해야될까요?
하늘을 날아다니는 날아가기(fly) 기능을 추가하는 방법에 대해 생각해보겠습니다.

1) Animal 클래스에 fly 메소드 추가

Animal 클래스에 fly 메소드를 추가하게되면 Animal 클래스를
상속 받는 객체도 fly 메소드를 호출할 때 fly() 기능으로
호출이 가능합니다.

그러나 동물 중에 모든 동물이 날 수 있는 것이 아닙니다.
공통 기능이 아니기 때문에 Animal 클래스에 추가한다면
상속 받고 있는 호랑이도 날 수 있는 상황이 발생할 수도 있습니다.
우리는 하늘을 나는 동물에게만 fly 메소드를 실행할 수 있게
해야하므로 이 방법에는 문제가 있습니다.










2) Fly 인터페이스를 구현

    이 방법은 fly() 메소드를 분리하여 인터페이스로 만든 후
    각 날아다니는 동물 클래스에서 구현하는 방법입니다. 이제
    호랑이는 날 수 없고 독수리는 날 수 있게 되었습니다.
    다른 날아다니는 동물(Dove)이 추가되어도 fly() 함수가
  필요하다면 Fly 인터페이스를 구현하면 됩니다.

  하지만 이 방법에도 문제가 있습니다.
  fly() 함수에 변경이 생길 경우인데, 기존에 fly() 메소드를
  호출하면 "날아 간다" 에서 "날개를 펄럭거리며 날아 간다"
  라고 변경을 해야된다면 fly() 함수의 내용을 변경하면
  될 것입니다.
    하지만 변경해야 하는 객체가 (새 종류 다수) 몇십, 몇백 개라
    고 한다면 그 모든 객체들의 fly() 메소드를 모두 변경해야
    합니다. 결국 중복이 발생한 것으로 좋은 방법이 아닌
    것입니다.

3) 같은 그룹을 묶어서 상속

이번엔 fly() 메소드를 구현해줄 객체를 새(Bird) 객체로 묶어서

계층적으로 상속했습니다. 이젠 fly() 메소드를 호출할 때

"날아 간다"에서 "날개를 펄럭거리며 날아 간다"로 변경하려면

Bird 클래스'만' 수정해주면 됩니다.

하지만 이 방법도 좋지 않습니다.

새 중에 날 수 없는 새들도 있기 때문입니다. 예) 펭귄

그런 동물들은 Bird 클래스를 상속하면 안됩니다.

그러면 어떻게 해야 할까? 날지 못하는 새 그룹을 생성할 것인가?

이 방법 또한 좋지 않아보입니다. 시스템이 확장되면서 그룹이 추가되고

새들의 공통 속성을 추가할 때면(부리 여부 등) 모든 새들의

그룹에 추가를 해야 합니다.

이렇게 되면 중복이 발생될 것이고 시스템이 더욱

복잡하게 될 것입니다.












디자인의 원칙을 생각해보면
"애플리케이션에서 달라지는 부분을 찾아내고 달라지지 않는 부분으로부터 분리 시킨다."
쉽게 풀어 말하자면
"바뀌는 부분은 따로 뽑아서 캡슐화시킨다. 그렇게 한다면 나중에 바뀌지 않는 부분은 영향을 미치지 않은 채
원하는 부분만 고치거나 확장할 수 있다."

그렇다면 바뀌는 부분을 찾아보겠스빈다. 날아가기(fly)와 울음(cry)가 동물에 따라 변화합니다.
움직이거나 모습을 보여주는 것은 통일한다는 가정을 두겠습니다.
그럼 날아가기(fly)와 울음(cry)을 Animal에서 따로 분리해보겠습니다.

스트래티지 패턴 - 교환 가능한 행동을 캡슐화하고 위임을 통해서 어떤 행동을 사용할지 결정한다.

(1) 변하는 부분 캡슐화

날아가기(fly)와 울음(cry)를 캡슐화 해보겠습니다.
먼저 Fly 클래스를 생성한 뒤 날아가기 종류별로 구현해보겠습니다.
날수 없다(FlyNoWay)와 날개로 날아간다(FlyWithWings)로 구현하겠습니다.

Fly 클래스 및 Fly를 상속 받은 클래스


Cry 역시 클래스를 생성한 뒤 3가지 울음 방식으로 구현하겠습니다.
새 울음(BirdCry), 울지 않음(CryNoWay), 호랑이 울음(TigerCry)로 구성되어 있습니다.
이것도 역시 새로운 울음 방식이 필요하다면 클래스를 생성한뒤 Cry를 상속 받으면 됩니다.

Cry 클래스 및 Cry를 상속 받은 클래스


(2) Animal 클래스

동물의 날아가기와 울음을 캡슐화하여 분리하였으므로 Animal 클래스에서는 fly()와 cry() 메소드를 제거합니다.
그리고 날아가기와 울음을 담당하는 클래스를 Animal 클래스에 추가해줍니다.
이제 날아가기와 울음을 요청할 수 있게 performFly()와 performCry() 함수를 추가합니다.
performFly()와 performCry() 함수는 Animal이 가지고 있는 fly, cry 객체 안의 fly(), cry() 함수를 호출합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Animal
{
private:
 
public:
    Cry* cry;
    Fly* fly;
    virtual void Display() = 0;
    virtual void Move() = 0;
 
public:
    void PerformCry() { this->cry->cry(); }
    void PerformFly() { this->fly->fly(); }
};
cs


(3) Animal 클래스

이제 Animal을 상속한 독수리(Eagle)와 호랑이(Tiger) 객체를 수정하겠습니다.

1
2
3
4
5
6
7
class Eagle : public Animal
{
public:
    Eagle();
    void Move() {};
    void Display() {};
};
cs
Eagle.h 소스입니다.

1
2
3
4
5
Eagle::Eagle()
{
    this->cry = new BirdCry();
    this->fly = new FlyWithWings();
}
cs
Eagle.cpp 소스입니다.

생성자에서 자신의 cry에 맞는 Cry 자식 클래스, fly에 맞는 Fly 자식 클래스를 선언하고 사용하면 됩니다.

자세히 이해가 안가실 수도 있는 분들이 계실거라 생각하고 샘플 소스 코드를 첨부하겠습니다.

- 샘플 코드 -


반응형

'- Programming > - 디자인 패턴' 카테고리의 다른 글

★ 2. 디자인 패턴 정리  (0) 2017.02.15
★ 1. 디자인 패턴 게시판  (0) 2017.02.15