디자인 패턴/Design Pattern

[디자인 패턴] 전략 패턴(Strategy Pattern)

JunsuKim 2022. 10. 10.
728x90

전략 패턴(Strategy Pattern)

알고리즘 군을 정의하고 캡슐화해주며, 서로 언제든지 바꿀 수 있도록 해주는 방법이다.

 

즉, 객체가 해야 하는 행위들을 각각 전략으로 만든 후, 동적으로 행위의 수정이 필요한 경우 전략을 바꿔주는 것만으로 수정하는 패턴이다.

왜 사용하는가?

예시를 들고, 이를 통해 전략 패턴을 왜 사용하는지 알아보자.

[디자인 패턴] 전략 패턴(Strategy Pattern) - undefined - 왜 사용하는가?

이처럼 Duck 클래스가 있다고 하자.

이 오리 클래스 안에는 quack 메서드와 swim 메서드, display 메서드가 있다.

이 Duck 클래스를 상속받는 MallardDuck과 RubberDuck 클래스 또한 울음소리를 낼 수 있고, 수영을 할 수 있다.

 

 

만약 여기서 Duck 클래스에 fly 메소드를 추가시킨다면 어떨까?

[디자인 패턴] 전략 패턴(Strategy Pattern) - undefined - 왜 사용하는가?

RubberDuck 클래스는 고무오리를 뜻하는 클래스이므로 fly 메서드가 적절하지 않다.

하지만 Duck 클래스를 받았기 때문에 fly 메소드를 갖게 되는 것이다.

이를 해결하기 위해, RubberDuck 클래스에서 fly 메서드를 빈 메서드로 정의하거나, 재정의하여 개별적으로 해결할 수 있다. 

하지만 이는 LSP(Lisvoke Substitution Principle)와 ISP(Interface Segregation Principle)에 위배되고, 코드를 수정하지 않으면 행위가 수정되지 않고, 코드를 수정해야만 행위를 수정할 수 있으므로 OCP(Open-Closed Principle)를 위배한다.

이러한 문제점을 해결하기 위해 전략 패턴(Strategy Pattern)을 사용한다.

전략 패턴(Strategy Pattern) 구현

위에서 예시로 들었던 Duck 클래스를 전략 패턴을 사용해보자.

각 오리들은 울음소리가 다르고, 날 수 있는지도 다르기에 두 기능을 오리 클래스에서 분리시켜 각각의 전략으로 선언한다.

두 전략을 구현하는 방법이 거의 동일하므로 fly 전략만을 보도록 하자.

 

fly() 메소드는 날 수 있다와 날 수 없다로 나뉘었다.

즉, 두개의 전략 클래스를 만든다.(FlyWithWings, FlyNoWay)

또한 이 두 전략을 캡슐화해주기 위해 FlyStrategy 인터페이스를 생성한다.

[디자인 패턴] 전략 패턴(Strategy Pattern) - undefined - 전략 패턴(Strategy Pattern) 구현

이를 코드로 보면 다음과 같다.

FlyStrategy

public interface FlyStrategy {
    void doFly();
}

FlyWithWings

public class FlyWithWings implements FlyStrategy {
    @Override
    public void doFly() {
        System.out.println("i can fly with my wings");
    }
}

FlyNoWay

public class FlyNoWay implements FlyStrategy {
    @Override
    public void doFly() {
        System.out.println("I can't fly");
    }
}

 이제 오리에 대한 클래스를 정의하자.

오리 클래스에는 display 메소드와 전략을 설정할 setFlyStrategy 외에 울음소리 전략을 설정할 setQuackStrategy 등 각 오리들에 공통적으로 필요한 메서드들이 들어있다.

[디자인 패턴] 전략 패턴(Strategy Pattern) - undefined - 전략 패턴(Strategy Pattern) 구현

위 클래스들을 코드로 봐보자.

Duck

import java.util.Objects;

public abstract class Duck {
    private FlyStrategy flyingStrategy;
    private QuackStrategy quackStrategy;

    public Duck(FlyStrategy flyingStrategy, QuackStrategy quackStrategy) {
        setFlyStrategy(flyingStrategy);
        setQuackStrategy(quackStrategy);
    }

    public void setFlyStrategy(FlyStrategy flyingStrategy) {
        this.flyingStrategy = Objects.requireNonNull(flyingStrategy);
    }

    public void setQuackStrategy(QuackStrategy quackStrategy) {
        this.quackStrategy = quackStrategy;
    }

    public void quack() {
        quackStrategy.doQuack();
    }

    public void swim() {
        System.out.println("I can swimming");
    }

    public void fly() {
        flyingStrategy.doFly();
    }

   public abstract void display();
}

MallardDuck

public class MallardDuck extends Duck {
    public MallardDuck(FlyStrategy flyingStrategy, QuackStrategy quackStrategy) {
        super(flyingStrategy, quackStrategy);
    }

    @Override
    public void display() {
        System.out.println("나는 청둥오리");
    }
}

RubberDuck

public class RubberDuck extends Duck {
    public RubberDuck(FlyNoWay flyNoWay, Squeak squeak) {
        super(flyNoWay, squeak);
    }

    @Override
    public void display() {
        System.out.println("나는 고무오리");
    }
}

Duck 클래스에서 각 전략들을 오리 클래스와 연결하기 위해 의존 관계를 주입해주는 것을 확인할 수 있다.

 

마지막으로 이 패턴이 잘 돌아가는지 확인해보도록 하자.

import java.util.ArrayList;
import java.util.List;

public class DuckClient {
    public static void flySimulation(Duck duck) {
        duck.display();
        duck.fly();
    }

    public static void swimSimulation(Duck duck) {
        duck.display();
        duck.swim();
    }

    public static void quackSimulation(Duck duck) {
        duck.display();
        duck.quack();
    }

    public static void main(String[] args) {
        List<Duck> ducks = new ArrayList<>();
        Duck duck1 = new MallardDuck(new FlyWithWings(), new Quack());
        Duck duck2 = new RedHeadDuck();
        ducks.add(duck1);
        ducks.add(duck2);
        Duck duck3 = new RubberDuck(new FlyNoWay(), new Squeak());
        ducks.add(duck3);

        System.out.println("오리 수영 시뮬레이션");
        for(Duck duck: ducks) swimSimulation(duck);
        System.out.println();

        System.out.println("오리 날기 시뮬레이션");
        for(Duck duck: ducks) flySimulation(duck);
        System.out.println();

        System.out.println("오리 울음소리 시뮬레이션");
        for(Duck duck: ducks) quackSimulation(duck);
        System.out.println();
    }
}

 

[디자인 패턴] 전략 패턴(Strategy Pattern) - undefined - 전략 패턴(Strategy Pattern) 구현

위와 같이 각 오리의 수영 여부, 날기 여부, 울음소리를 수행시간동안 각각 다르게 출력하는 것을 확인할 수 있다.

전략 패턴(Strategy Pattern)의 장단점

장점

  • 위임이라는 느슨한 연결을 통해 전략을 쉽게 바꿀 수 있고, 실행 시간에 행위를 변경할 수 있다.
  • 새 전략을 추가하기 쉽다.
  • 여러 행위가 전략 패턴으로 구현될 경우, 이들의 조합으로 객체를 쉽게 구성할 수 있다.

단점

  • 행위의 모델링이 쉽지 않을 수도 있다
728x90

댓글