디자인 패턴

전략 패턴 (Strategy Pattern)

개발정리 2022. 8. 13. 15:13

전략 패턴


여러 알고리즘들을 캡슐화하고 상호 교환 가능하게 만드는 패턴

  • 컨텍스트에서 사용할 알고리즘들을 클라이언트에서 선택한다.

 

즉, 특정 업무를 수행하는 방법이 여러가지 일 경우 여러가지 방법들을 각각의 클래스로 캡슐화하면 캡슐화된 것을 공통 인터페이스로 추상화하여 업무를 수행하는 Client 에서는 공통 인터페이스를 사용하게 하여 Client 코드는 변경되지 않고도 여러 방법들을 바꿔 낄 수 있다.

 

 

  • Context : 원래 로직을 수행하던 클래스
  • ConcreteStrategy : 각각의 알고리즘들이 구현된 클래스
  • Strategy : 추상화된 알고리즘 수행 인터페이스

 

 

 

전략 패턴 적용 전


"무궁화 꽃이 피었습니다" 를 실행하는 프로그램이 있다.

 

이 게임은 "무궁화 꽃이" 까지는 파란불이며, "피었습니다." 까지는 빨간불로 구현을 하였고 여기에 Speed 라는 필드를 정의하여 "무궁화 꽃이" 혹은 "피었습니다." 를 말하는 속도를 조절 할 수 있도록 하는 기능을 넣었다.

 

 

BlueLightRedLight

public class BlueLightRedLight {
  private int speed;

  public BlueLightRedLight(int speed) {
    this.speed = speed;
  }
  
  public void blueLight() {
    if (speed == 1) {
      System.out.println("무 궁 화 꽃 이");
    } else if (speed == 2) {
      System.out.println("무궁화 꽃이");
    } else if (speed == 3){
      System.out.println("무광꼬치");
    }
  }
  
  public void redLight() {
    if (speed == 1) {
      System.out.println("피 었 습 니 다.");
    } else if (speed == 2) {
      System.out.println("피었습니다.");
    } else if (speed == 3){
      System.out.println("펴씀다");
    }
  }
}

 

 

문제점

speed 에 따라 분기를 나눠야하고 다르게 행동해야 한다. 

또한 다르게 행동하기 위해 Client 에서 Speed 를 1, 2, 3 과 같은 구체적인 숫자를 바꿔줘야하고 새로운 Speed 4 가 생기면 기존 코드를 수정해야 하는 문제가 발생한다.

 

이를 전략 패턴을 이용하여 해결해보자.

 

 

 

전략 패턴 적용 후


 

먼저 Strategy 인터페이스를 정의한다.

// Strategy
public interface Speed {
  void blueLight();
  void redLight();
}

 

 

ConcreteStrategy 를 구현하자.

Slow, Normal, Faster 를 정의하여 이전에 방식의 speed 1, 2, 3 에 해당되는 기능을 구현해준다.

public class Slower implements Speed {
  @Override
  public void blueLight() {
    System.out.println("무 궁 화 꽃 이");
  }

  @Override
  public void redLight() {
    System.out.println("피 었 습 니 다.");
  }
}
public class Normal implements Speed {
  @Override
  public void blueLight() {
    System.out.println("무궁화꽃이");
  }

  @Override
  public void redLight() {
    System.out.println("피었습니다.");
  }
}
public class Faster implements Speed {
  @Override
  public void blueLight() {
    System.out.println("무광꼬치");
  }

  @Override
  public void redLight() {
    System.out.println("펴씀다.");
  }

 

 

Context 인 BlueLightRedLight 는 아래와 같다.

// Context
public class BlueLigthRedLight {
  private Speed speed;

  public BlueLigthRedLight(Speed speed) {
    this.speed = speed;
  }
  
  public void blueLight() {
    speed.blueLight();
  }
  
  public void redLight() {
    speed.redLight();
  }
}

생성자에서 Speed 타입 클래스 (Slower, Normal, Faster) 중 하나를 받아 그에 따르는 로직을 처리한다.

 

 

Client 는 아래와 같이 호출 할 수 있다.

public class Client {
  public static void main(String[] args) {
    BlueLigthRedLight blueLigthRedLight = new BlueLigthRedLight(new Slower());
    blueLigthRedLight.blueLight();
    blueLigthRedLight.redLight();
  }
}

 

 

만약, BlueLight 와 RedLight 의 속도를 다르게 하고 싶으면 생성자가 아닌 메소드에서 Speed 타입 클래스를 인자로 받으면 된다.

public class BlueLigthRedLight {

  public void blueLight(Speed speed) {
    speed.blueLight();
  }

  public void redLight(Speed speed) {
    speed.redLight();
  }
}
public class Client {
  public static void main(String[] args) {
    BlueLigthRedLight blueLigthRedLight = new BlueLigthRedLight();
    blueLigthRedLight.blueLight(new Slower());
    blueLigthRedLight.redLight(new Faster());
  }
}

 

 

 

장점과 단점


장점

  • 새로운 전략을 추가하더라도 기존 코드를 변경하지 않는다. (OCP)
  • 상속 대신 위임을 사용할 수 있다.
  • 런타임에 전략을 변경할 수 있다.

 

단점

  • 복잡도가 증가한다.
  • 클라이언트 코드가 구체적인 전략을 알아야 한다.

 

 

 

실무에선 어떻게 쓰이나?


자바의 Comparator

public class StrategyInJava {
  public static void main(String[] args) {
    List<Integer> numbers = new ArrayList<>();
    numbers.add(2);
    numbers.add(1);
    numbers.add(3);
    numbers.add(5);
    numbers.add(4);

    Collections.sort(numbers, new Comparator<Integer>() {
      @Override // ConcreteStrategy
      public int compare(Integer o1, Integer o2) {
        return o1 - o2;
      }
    });
  }
}

 

 

 

Spring 의 ApplicationContext 등등

public class StrategyInSpring {
  public static void main(String[] args) {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext();
    ApplicationContext applicationContext1 = new FileSystemXmlApplicationContext();
    ApplicationContext applicationContext2 = new AnnotationConfigApplicationContext();

    BeanDefinitionParser parser;

    CacheManager cacheManager; // 다양한 캐시 전략을 제공 
  }
}

 

 

 

참고