본문 바로가기
디자인 패턴

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

by doodoom 2022. 9. 29.

0. 이 글을 쓰게 된 이유

전략 패턴은 평소에 많이 사용하고 기본적인 패턴 중 하나이다. 이 전에도 정리한 적이 있었는데 부족한 내용이 있었기도 하고, 템플릿/콜백 패턴을 공부 하는데에 일단 전략 패턴부터 다시 정리하고 넘어가면 좋겠다 생각이 들어 정리한다.

1. 전략 패턴이란?

전략 패턴이란 행동 패턴 중 하나로, 동일 계열의 알고리즘 군을 정의하고, 각 알고리즘을 캡슐화하며(인터페이스를 통해), 이들을 상호교환이 가능하도록 만든다. 알고리즘을 사용하는 클라이언트와 상관없이 독립적으로 알고리즘을 다양하게 변경할 수 있다.


Context 클래스는 Strategy 인터페이스 타입의 클래스를 필요하도록 설계 되었다고 하자.
Strategy 인터페이스를 구현하는 클래스인 ConcreteStrategyA, ConcreteStrategyB, ConcreteStrategyC가 있다.
이때 Context 클래스를 사용하는 클라이언트는 생성자 혹은 수정자를 통해 Strategy를 구현한 클래스를 주입해주면 그 Context 객체는 주입한 객체의 알고리즘을 사용하게 된다. 즉, 다형성을 활용해 객체 간의 결합을 느슨하게 만드는 방법이다.

추상적인 설명만으로는 어렵다고 느낄 수 있으니 예시를 보자.

2. Exmaple

2.1 Context(Food)

public class Food {

    private FoodStrategy foodStrategy;

    public Food(FoodStrategy foodStrategy) {
        this.foodStrategy = foodStrategy;
    }

    public void getFood() {
        foodStrategy.makeFood();
    }
}

Food라는 클래스가 있다고 하자. 이 클래스는 FoodStrategy 인터페이스 타입을 주입 받아 메소드에서 사용한다.

2.2 Strategy(FoodStrategy)

public interface FoodStrategy {
    void makeFood();
}

FoodStrategy라는 인터페이스가 있고 거기에 makeFood라는 메소드(알고리즘을 담고있는)를 구현하도록 강제한다.

2.3 ConcreteStrategy(KoreaFoodStrategy)

public class PizzaStrategy implements FoodStrategy {
    @Override
    public void makeFood() {
        System.out.println("빵 반죽하고 치즈 넣고 토핑 올리고 오븐에 굽기");
    }
}

PizzaStrategy는 FoodStrategy를 구현한 클래스이다.
makeFood() 메소드를 오버라이딩해서 알고리즘을 구현했다.

2.4 Client

public static void main(String[] args) {
        Food pizza = new Food(new PizzaStrategy());
        pizza.getFood();
    }

클라이언트 코드에서 Food라는 Context클래스에 PizzaStrategy를 생성해서 넣어주면 그 객체는 피자를 만드는 알고리즘이 담긴 인스턴스가 된다.

정리

위 코드는 간단한 전략 패턴의 예시라고 볼 수 있다.
이렇게 하면 Food와 FoodStrategy와의 관계를 느슨하게 인터페이스로 정의함으로써 다양한 알고리즘을 사용할 수 있다.
다형성을 활용한 대표적인 전략이라고 볼 수 있다.

3. 전략 패턴의 장점

만약 위 예시를 확장한다고 해보자. 새로운 음식인 Chicken이 있다.

public class ChickenStrategy implements FoodStrategy {
    @Override
    public void makeFood() {
        System.out.println("닭 손질하고 분리하고 튀김가루 묻혀서 튀김.");
    }
}

이렇게 새로운 전략을 만들고 바로 사용하면 된다.

Food chicken = new Food(new ChickenStrategy());
chicken.getFood();

3.1 장점 정리

위 코드를 보면 기존 코드에 수정이 일어나지 않는다. 그 대신 확장되는 기능에 대해 추가만 하면 된다.
이는 OCP(개방폐쇄 원칙)을 잘 지킨다. 즉, 이 패턴은 변경에는 닫혀있고 확장에는 열려있다.

4. 전략 패턴의 단점

  1. 사용자가 서로 다른 전략을 모두 알아야한다.
    적당한 전략의 선택하기 위해 모든 전략에 대해서 이해하고 있어야 한다.
  2. Strategy객체와 Context 객체 사이에 의사소통 오버헤드가 있다.
    모든 ConcreteStrategy 클래스는 Strategy 인터페이스를 공유한다.
    이는 모든 ConcreteStrategy 클래스는 Strategy의 모든 정보를 가지고 있어야한다는 말이다.
    간단한 ConcreteStrategy는 이 정보들을 일부만 사용하거나 하나도 사용하지 않을 수도 있다.
    즉, 사용되지도 않을 정보들을 Context 객체가 생성하고 초기화할 때에도 있다는 말이다.
    이 때에는 Strategy를 분리하거나 다른 패턴을 사용하여 Strategy객체와 Context 객체 사이에 결합도를 높히는게 좋을 수도 있다.
  3. 객체 수가 증가한다.
    많은 전략들이 생기면 어플리케이션 내에 객체 수가 증가할 수 있다.

5. 전략 패턴의 활용성

  • 행동들이 즈금씩 다를 뿐 개념적으로 관련된 많은 클래스들이 존재할 때, 전략 패턴은 많은 행동 중 하나를 가진 클래스를 구성할 수 있는 방법을 제공한다.
  • 사용자가 몰라야 하는 데이터를 사용하는 알고리즘이 있으 때, 노출하지 말아야 할 복잡한 자료구조는 Strategy 클래스에만 두면 되므로 사용자는 몰라도 된다.
  • 하나의 클래스가 많은 행동을 정의하고, 이런 행동들이 그 클래스의 연산 안에서 복잡한 다중 조건문의 모습을 취할 때, 많은 조건문보다는 각각을 Strategy 클래스로 옮겨놓는 것이 좋다.