디자인 패턴/Design Pattern

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

JunsuKim 2022. 10. 10.
728x90

전략 패턴(Strategy Pattern)

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

 

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

왜 사용하는가?

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

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

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

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

 

 

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

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 인터페이스를 생성한다.

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

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 등 각 오리들에 공통적으로 필요한 메서드들이 들어있다.

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

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)의 장단점

장점

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

단점

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

댓글