본문 바로가기
Design pattern

스트래티지 패턴(strategy pattern) 정리

by 평범한 개발자... 2020. 5. 20.
스트래티지 패턴(strategy pattern)

스트래티지 패턴(strategy pattern) 이란?

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

 

  • 이해하였는가?

    • 디자인패턴은 정의와 예제만 읽고 이해할 수 없다. 왜 이런 패턴이 필요하고 없으면 어떤게 무엇인지 정확하게 알고 이해하는 것이 중요하다.
    • 이 글을 읽고나서 다시 정의를 읽어보면 그 땐 모두 이해가 갈 것이다!

 

1. 일반 객체지향 기법으로 구현을 했을 때의 문제

1-1. 간단한 오리 연못 시뮬레이션 게임을 만들어보자

  • 여러분이 오리 연못 시뮬레이션 게임을 만드는 회사에 다니고 있다고 가정해보자.

  • 이 게임에서는 헤엄치고 꽥꽥 울음 소리를 내는, 매우 다양한 오리를 보여준다.

  • 그래서 객체지향 기법을 이용하여 Duck이라는 수퍼클래스를 만들고, 이 클래스를 상속받아 다양한 종류의 오리 클래스를 만들었다.

  • 요구사항

    • 모든 오리는 꽥꽥 소리를 낼 수 있고, 헤엄을 칠 수 있다.
    • 오리는 모양새가 다르다.
  • 결과

    • 공통사항을 하나로 묶은 Duck 추상 클래스 생성

      • quack: 꽥꽥 우는 소리는 내는 기능
      • swim: 수영하는 기능
      • display: 인터페이스(오리는 모양이 다르기 때문에 하위 클래스에서 구현)
    • Duck 클래스를 상속받아 다양한 오리 클래스 생성

      • 청둥오리
      • 미국흰죽지
      • 고무오리

image

 

1-2. 요구사항의 변경이 생겼다.

  • 회사 임원들이 이 게임은 오리가 날라다닐 수 있도록 해야 대박이 터질거라고 이야기했다.
  • 여러분은 Duck 클래스에 fly()메소드만 추가하면 아주 쉽게 모든것이 해결 될거라고 생각할 것이다.

 

1-3. 그런데 심각한 문제가 발생했다. (문제1)

  • 상속 받은 모든 서브클래스 오리가 날 수 있는게 아니라는 것을 깜빡했다.

  • fly() 메소드가 추가되면서 고무오리에게 적합하지 않은 행동이 추가됐다.

  • 한 부분의 코드 추가로 전체 프로그램에 부작용이 나타난 것이다.

  • 우선 메소드를 재정의 함으로써 급한불을 껐다.

  • 만약 울지도 못하고 날지도 못하는 나무 오리가 추가되면 어떻게 해야할까?

  • 클래스를 상속받고, 아무것도 수행하지 않도록 또 재정의 해야한다.

  • 객체지향 기법으로 구현을 했지만, 과연 이게 효율적인 프로그래밍인 것인가? 하고 의심이 들기 시작한다.

 

1-4. Duck 클래스의 단점

  • 서브클래스에서 코드가 중복된다.

    • 슈퍼클래스에서 제공하는 기능과 다른 기능을 구현하고 싶을 땐, 하위 클래스에서 메소드를 재정의한다.
    • 이런 하위 클래스가 여러 개일 경우, 같은 코드를 계속 재정의 함으로써 코드가 중복돤다.
  • 프로그램 실행 시, 오리의 특징을 바꾸기 힘들다.

    • 한 번 천둥오리로 생성된 클래스는 프로그램이 종료될때까지 천둥오리로 남아있다.
  • 모든 오리의 행동을 알기 어렵다.

    • 변수가 참 많습니다. 오리가 날거라고 상상이나 했겠습니까?
    • 다음에는 또 어떤 엉뚱한 기능들이 추가될까요
  • 코드를 변경했을 때, 원치 않는 영향을 끼칠 수 있다.

    • 기능을 추가해달라고 해서 추가했는데, 심각한 문제가 발생했다.

 

2. 인터페이스 활용

2-1. 상속대신 인터페이스를 사용한다면?

  • 위의 문제를 해결하기 위해, 오리마다 달라질 수 있는 기능들을 인터페이스로 구현해보자
  • 이렇게 하면 날 수 있는 오리들은 Flyable 인터페이스를 구현하면 될 것이고,
  • 꽥꽥 우는 오리들은 Quackble 인터페이스 구현하면 되겠다.
  • 그리고 새롭게 추가되는 기능들은 ~ble 형태의 인터페이스를 만들어서 추가하면된다.
  • 해당 기능이 없는 오리들은 인터페이스를 상속받지 않으면 된다.

ㅅㄷㄴㅅ

2-2 이 또한 문제다. (문제2)

  • 기존에 있는 기능에 문제가 생겨 수정해야하는 경우가 생겼다고 가정해보자.

  • 날아다니는 동작을 조금 바꾸기위해, Flyble을 인터페이스로 구현한 모든 서브 클래스들을 다 고쳐야한다.

    • 만약 Flyble을 상속받아 구현한 오리 클래스가 50개가 넘어간다면...? 끔찍할 것이다..
  • 즉, 이러한 구조로는 코드를 재사용할 수 없다.

    • Flyble과 Quackble은 코드가 없는 인터페이스이기 때문

 

이러한 문제를 해결하기 위해 디자인패턴을 배우는 것이고, 스트래티지 패턴을 설명하기 앞서 장황하게 문제점들을 설명했습니다.

 

3. 문제 파악 하기

  • 상속을 사용하는 것이 그리 성공적이지 못하다는 것을 확실히 알게되었고, Flyable과 Quackable와 같이 인터페이스를 사용하는 방법은 문제를 해결하는 듯 싶었으나 코드를 재사용 할 수 없다는 문제점이 있었다.

  • 디자인 원칙1

    • 애플리케이션에서 달라지는 부분을 찾아내고, 달라지지 않는 부분으로부터 분리시킨다.
    • 즉, 바뀌는 부분은 따로 캡슐화시킨다. 그렇게 하면 나중에 바뀌지 않는 부분에는 영향끼치지 않고, 바뀌는 부분만 고치거나 확장할 수 있다.
  • Duck 클래스를 분석해보자

    • fly()와 quack()은 기존 Duck클래스에서 달라지는 부분이기 때문에, 이러한 행동을 클래스에서 끄집어내어 각 클래스 집합 형태로 만들어 보자

       

3-1 행동 디자인

  • 최대한 유연하게 만드는 것이 중요하다.

    • 애초에 오리의 행동들이 유연하지 못해 발생한 문제들이었다.
    • 프로그램 시작 시, 오리의 행동을 초기화 하거나 중간에 동적으로 변경할 수 있게 만들어야한다.
  • 각 행동은 인터페이스로 표현하고,

    • 예) FlyBehavior, QuackBehavior
  • 각 행동을 구현할 때 해당 인터페이스를 구현하도록 만든다.

    • 예) FlyWithWings, MuteQuack 등

     

sdf

quack

  • 디자인 원칙2

    • 구현이 아닌 인터페이스에 맞춰서 프로그래밍한다.
  • 행동에 따라서 각 인터페이스의 구현이 집합 클래스 형태로 나타난다.

    • FlyBehavior 인터페이스 집합: FlyWithWings, FlyNoWay 클래스
    • QuackBehavior 인터페이스 집합: Quack, MuteQuack, Squeak 클래스
  • 오리의 기능이 바뀌어도 언제든지 재사용할 수 있으며, 새로운 기능이 추가되어도 기존의 코드에는 영향을 끼치지 않는다.

    • 예를들어, 꽥꽥 소리를 내는 Quack클래스를 사용하다가 그 오리는 소리를 내지 않아야 한다면 MuteQuack 클래스를 사용 하면 된다.
    • 꺼억꺼억 소리를 내는 새로운 클래스가 필요하다면, QuackBehavior 인터페이스를 상속받아 새로운 클래스를 구현하면 된다.

 

  • 디자인 원칙3

    • 상속보다는 구성을 활용한다.

      • 아래에서 설명할 Duck 클래스는 위에 구현된 행동 클래스 집합들을 상속받는 것이 아니라, Duck클래스 안에 구성됨으로써 행동을 부여받게 된다.
      • 이러한 구성을 활용하여 시스템을 만들면 유연성을 크게 향상시킬 수 있다.

 

4. 행동기반의 Duck 클래스

  • 우리는 위에서 여러 행동 클래스들을 구현했다. 이제는 Duck 클래스를 구현할 차례이다.

  • Duck 클래스는 그저 각 인터페이스의 인스턴스변수를 추가하면 된다.

    • 즉, 우는 행동과 나는 행동은 Duck 클래스에 구성된다는 뜻이다.

 

 

  • 기존 Duck 코드와 비교해보면, 행동관련 메소드는 Duck클래스에서 직접 수행하는 것이 아니라, 각 행동클래스에게 위임했다는 것을 알 수 있다.

  • 이 코드에서는 오리가 어떤 소리를 내는지, 어떻게 나는지 중요하지 않다. 그저 해당 행동을 실행시킬 수 있다는 것이 중요하다.

  • 어떤 행동이 수행되는지는 각 멤버 인스턴스에 할당되는 행동 클래스에 따라 달라질 것이다.

  • 이제 이 Duck 클래스를 이용하여 실제 오리 클래스를 만들어보자

     

4-1 MallardDuck

image

  • 핵심은 다형성이다.

    • 프로그램의 기능이 유연하게 바뀌기 위해서는 다형성이 굉장히 큰 핵심이다.

    • 오리와 오리의 행동이 고정된 것이 아니라, 아래와 같이 다형성에 의해 얼마든지 바뀔 수 있다는 것

    • 하지만 현재 위의 코드 자체는 유연하여 코드를 바꿔 쉽게 동작을 바꿀 순 있지만, 프로그램 실행 중에 동작을 바꿀 순 없다.

    • 파라미터를 활용하여 동적으로 바뀌도록 조금 고쳐보자

       

4-2 동적인 Duck 클래스

  • 사실 말은 거창하지만, Duck클래스에 그저 set 메소드를 추가하여 행동을 할당하도록 구현하면 된다.

image

 

5. 정리

  • 지금까지 오리 시뮬레이터 디자인을 아주 깊이 파헤쳐보았다.

    • 오리들은 Duck클래스를 상속받아 확장해서 만들었고

    • 오리마다 달라질 수 있는 행동은 FlyBehavior와 QuackBehavior를 구현해서 만들었다.

    • 이번에는 생각을 조금 바꿔, 이 일련의 행동들을 알고리즘군으로 생각해보자

       

  • 이제 아래 큰 그림을 보면서 스트래티지 패턴이 정확히 무엇인지 알아보자

    • 클라이언트에서는 각각 캡슐화된 알고리즘군(오리의 행동)을 활용한다.

    • FlyWithWings, MuteQuack 이러한 클래스들은 알고리즘군이다.

    • 이러한 알고리즘군은 바뀔수도 있고, 새롭게 추가 될 수 있다.

      • 해당 패턴에서는 새로운 기능이 추가될 때 다른 알고리즘에 영향을 끼치지 않는다.

        • 1-3 항목 문제 해결
      • 기능이 바뀌거나 버그가 생겨 코드가 변경되면 해당 알고리즘만 고치면 된다.

        • 2-2 항목 문제 해결

       

그림1

 

5-1. 이제 다시 스트래티지 패턴의 정의를 읽어보자.

  • 스트래티지 패턴(strategy pattern) 이란?

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

 

 

Head First Design Patterns 라는 책을 읽고 정리하였습니다.

디자인패턴 처음 공부할 때 읽기 좋은 책인 것 같습니다. 모든 디자인패턴을 다루진 않지만,

중요한 디자인패턴은 모두 다루네요.

GoF패턴책으로 공부하다가 너무 어려워서 포기했는데 이렇게 좋은책이 있었네요.

반응형

'Design pattern' 카테고리의 다른 글

데코레이터 패턴(decorator pattern) 정리  (0) 2020.07.06
옵저버 패턴(observer pattern) 정리  (0) 2020.06.17

댓글