Lee's Grow up

[디자인패턴/Design Pattern] Strategy 패턴 / 전략 패턴 본문

PROGRAMMING/디자인패턴

[디자인패턴/Design Pattern] Strategy 패턴 / 전략 패턴

효기로그 2019. 12. 11. 13:21
반응형

관련 내용은[자바 언어로 배우는 디자인 패턴 입문], [Head First Design Pattern] 의 내용을 참고해서 정리한 내용입니다. 잘못된 부분은 댓글로 지적해주시면 감사하겠습니다.

1. Strategy 패턴이란?


알고리즘군을 정의하고 각각을 켑슐화하여 교환해서 사용할 수 있도록 만드는 방식, Strategy 패턴을 활용하면 알고리즘을 사용하는 클라이언트와는 독립적으로 알고리즘을 변경할 수 있다.
즉 알고리즘의 인터페이스(API) 부분만 규정해서 변경해서 사용할 수 있도록 하는 것

2. Strategy 패턴의 등장 인물


  • Strategy(전략)
    1. 전략을 이용하기 위한 인터페이스(API)를 결정합니다.
  • ConcreteStrategy(구체적인 전략)
    1. Strategy를 실제로 구현하는 역할
  • Context(문맥)
    1. Strategy의 인터페이스(API)를 호출해서 이용하는 역할
2-1. Strategy 패턴의 클래스 다이어그램

3. 예제


예제를 통해서 내용을 구체화해보겠습니다.
우선 Duck라는 부모 클래스와 하위 클래스로 MallardDuck, RedheadDuck가 존재한다고 가정합니다.
이때 우리는 객체지향적인 특성을 사용하기 위해 상속과, 추상화를 통해 공통된 부분을 부모 클래스에 정의하고 공통되지 않는 부분을 자식 클래스에서 정의하려고 할 것입니다.

3-1. 예제에 사용될 클래스 다이어그램

3-2. Duck 클래스 ( 부모 클래스 )
public abstract class Duck {

    public void swim() {
        System.out.println("오리가 수영을 합니다.");
    }

    public void quack() {
        System.out.println("꽥꽥 웁니다.");
    }

    public abstract void display();
}
3-3. 자식클래스 ( MallardDuck, RedheadDuck, DecoyDuck )
public class MallardDuck extends Duck  {

    @Override
    public void display() {
        // TODO Auto-generated method stub
        System.out.println("청둥오리입니다.");
    }

}

public class RedheadDuck extends Duck{

    @Override
    public void display() {
        System.out.println("붉은 머리 오리입니다.");
    }
}

이런 식으로 우리는 객체지향적으로 객체를 설계할 것입니다. 그런데 추가로 짖지도 않고, 날지도 못하는 가짜 오리인 DecoyDuck가 추가가 된다고 하면? 아래와 같이 정의해야 할 것입니다.

3-4. DecoyDuck
public class DecoyDuck extends Duck{

    @Override
    public void display() {
        System.out.println("장난감 오리입니다.");
    }

    @Override
    public void quack() {
        System.out.println("장난감이 삐용삐용 하고 소리를 냅니다.");
    }
}

나아가 갑자기 오리가 날아가는 기능을 추가해달라고 했습니다. 그렇다면 여러분은 공통기능이라고 생각해 Duck 클래스에
public void fly() { System.out.println("오리가 날아갑니다"); } 라는 메소드를 추가했다고 하면 우리는 가짜 오리인 DecoyDuck에도 추가로 오버라이딩을 해줘야 하는 일이 발생합니다. 이와 같이 코드의 한 부분이 바뀜으로써 전체 프로그램에 수정, 부작용이 발생하게 됩니다.

4. 해결


4-1. 예제에 사용될 클래스 다이어그램

위와 같은 문제로 인해 날기와, 짖기의 행동 자체를 구현이 아닌 inteface(API)로 정의해 사용하는 방식을 사용합니다.

4-2. fly() 관련 전략 생성
public interface FlyStrategy {
    public void fly();
}

public class FlyWithWingStrategy implements FlyStrategy {
    @Override
    public void fly() {
        System.out.println("오리가 날아가고 있습니다.");
    }
}

public class FlyNoWayStategy implements FlyStrategy {
    @Override
    public void fly() {
        System.out.println("이 오리는 날 수 없습니다.");

    }
}

이렇게 행동 자체를 캡슐화해버리는 방식입니다. 마찬가지로 분리시킨 행동인 짖기에 관해서도 추가해주면 됩니다.
그리고 Duck에서 직접적으로 구현했던 fly()부분을 아래와 같이 인터페이스를 comsition (구성)해주고, 해당 인터페이스의 메소드를 사용하는 방식으로 변경합니다.

public abstract class Duck {

    private FlyStrategy flyStrategy;

    public void fly() { flyStrategy.fly();}

    public void swim() { System.out.println("오리가 수영을 합니다.") ; }

    public abstract void display();

    public void setFlyStrategy(FlyStrategy flyStrategy) {
        this.flyStrategy = flyStrategy;
    }
}

여기서 Strategy를 초기화하는 방식으로는 하위 클래스에서 생성자로 컨트롤할 수도 있고 그건 상황에 따라서 사용하시면 될 것 같습니다. 이렇게 사용하면 아래와 같이 사용이 가능하며, 행동을 원할 때 변경할 수 있습니다.

4-3. Main 클래스
public class Main {
    public static void main(String[] args) {
        MallardDuck mallarDuck = new MallardDuck();

        mallarDuck.setFlyStrategy(new FlyWithWingStrategy());
        mallarDuck.fly();

        mallarDuck.setFlyStrategy(new FlyNoWayStategy());
        mallarDuck.fly();
    }
}
4-4. 실행 결과
오리가 날아가고 있습니다.
이 오리는 날 수 없습니다.
4-5. 람다 표현식으로 리팩토링

위 예제에서 전략에 해당하는 FlyStrategy 인터페이스는 선언된 메서드가 1개뿐이기 때문에, 함수형 인터페이스라는 조건에 해당한다. 그래서 람다 표현식으로 4-3의 코드를 아래처럼 변화 가능

    mallarDuck.setFlyStrategy(() -> System.out.println("오리가 날아가고 있습니다."));
    mallarDuck.fly();

    mallarDuck.setFlyStrategy(() -> System.out.println("오리가 날아가고 있습니다."));
    mallarDuck.fly();

다만, 구현해야하는 내용이 지역변수를 참조한다거나, 복잡한 과정, 재사용이 많을 경우는 기존의 방식처럼 클래스를 이용한 방법이 조금 더 나을 수 있다. 전략 패턴의 경우 람다로도 사용이 가능하다고 인지하고, 상황에 맞는 방식을 사용하는 것을 권장합니다. 람다란

왜 사용하는 것일까?
  • 위임이라는 느슨한 연결을 사용하고 있으므로 알고리즘을 용이하게 교환할 수 있습니다.

5. 관련 패턴


  • Flyweight패턴 : ConcreteStrategy 역할은 해당 패턴을 사용해서 복수의 장소에서 공유 가능
  • Abstract Factory 패턴 : 알고리즘뿐만 아니라 공장, 부품, 제품을 모두 교체 가능
  • State 패턴 : 비슷한 방식이지만, 알고리즘이 아닌 '상태' 를 변화하는 방식을 위임

6. 비교하기


디자인 패턴을 배우다 보니까 Strategy , Template Method 가 얼핏 보면 비슷하다고 느낌이 들어서 차이점을 정리합니다.

  • Strategy : 알고리즘을 구성으로 사용.유연성link
  • Template Method : 알고리즘을 서브클래스에서 일부 지정할 수 있으면서 재사용이 가능, 하지만 의존성이 크다는 문제가 발생. 재사용link
반응형
Comments