디자인 패턴(구)/행위 패턴

전략(Strategy) 패턴이란?

공대키메라 2022. 4. 27. 23:06

1. 전략 패턴이란?

의도

동일 계열의 알고리즘군을 정의하고, 각 알고리즘을 캡슐화하여, 이들을 상호교환이 가능하도록 만든다.

알고리즘을 사용하는 클라이언트와 상관없이 독립적으로 알고리즘을 다양하게 변경할 수 있게 한다. 

 

다시 말하면, 객체들이 할 수 있는 행위 각각에 대해 전략 클래스를 생성(알고리즘군 정의)하고,

 

유사한 행위들을 캡슐화 하는 인터페이스를 정의하여(알고리즘을 캡슐화),

 

객체의 행위를 동적으로 바꾸고 싶은 경우 직접 행위를 수정하지 않고 전략을 바꿔주기만 함으로써 행위를 유연하게 확장하는 방법을 말한다.(이들을 상호교환이 가능하도록 만든다.)

 

이름에서 볼 수 있듯이, 객체의 행위를 우리가 원할 때, 즉 전략에 맞도록 변경하는 패턴이다. 

사용시기

  • 행동들이 조금씩 다를 뿐 개념적으로 관련된 많은 클래스들이 존재할 때. 전략 패턴은 많은 행동 중 하나를 가진 클래스를 구성할 수 있는 방법을 제공한다. 
  • 알고리즘의 변경이 필요할 때. 하나의 처리 과정에 대해 절충에 따라 다른 알고리즘을 정의해 적용할 수 있다.
  • 사용자가 몰라야 하는 데이터를 사용하는 알고리즘이 있을 때, 노출하지 말아야 할 복잡한 자료 구조는 Strategy 클래스에만 두면 되므로 사용자는 몰라도 된다. 
  • 하나의 클래스가 많은 행동을 정의하고 이런 행동들이 그 클래스 연산 안에 복잡한 다중 조건문 형태를 취할 때.

구조

  • Strategy : 제공하는 모든 알고리즘에 대한 공통의 연산들을 인터페이스로 정의
  • ConcreteStrategy : Strategy 인터페이스를 실제 알고리즘으로 구현
  • Context : ConcreteStrategy 객체를 통해 구성된다. Strategy객체에 대한 참조자를 관리하고, 실제로는 Strategy 서브 클래스의 인스턴스를 갖고 있음으로 구체화한다. 또한, Strategy 객체가 자료에 접근해가는 데 필요한 인터페이스를 정의 

2. 구현

구현(Implementation) 동화

키메라는 퇴근 후에 저녁을 직접 만들어 먹는다.

배달 음식은 너무 경제적으로 부담이 되기도 하고 

무엇보다도 좋아하는 음식을 양껏 먹지를 못하는게 너무 싫다. (이거는 그냥 스토리가 아니라 진심이다.)

그래서 재료를 인터넷 혹은 식자재 마트에서 대량 구매해 냉장고에 손질을 해 놓는다. 

문제는 저녁을 만드는 시간이 굉장히 피곤하다는 것이다. 

어느날은 느긋하게 준비하는가 하면, 어느날은 빨리 먹고 정리하고 싶기도 하다.

코드를 통해 키메라는 이것을 제어해볼 것이다. 전략적으로(strategycally) 말이다

DinnerStrategy.java

public interface DinnerStrategy {

    void makeDinner();

    void doTheDishes();

}

키메라의 저녁 전략 틀은 저녁을 만들고(makeDinner()), 설거지를 하는 것이다(doTheDises())

 

우선 평상시 모습을 구현 클래스로 만들겠다.

NormalDinner.java

public class NormalDinner implements DinnerStrategy {
    @Override
    public void makeDinner() {
        System.out.println("배고프네~ 밥먹자~ 구구구~");
    }

    @Override
    public void doTheDishes() {
        System.out.println("밥을 먹엇으면 설거지를 해야지");
    }
}

DinnerContext.java

public class DinnerContext {

//   1. 생성자를 주입하는 방식
//    private DinnerStrategy dinnerStrategy;
//
//    public DinnerContext(DinnerStrategy dinnerStrategy) {
//        this.dinnerStrategy = dinnerStrategy;
//    }
//
//  public void makeDinner(){
//      dinnerStrategy.makeDinner();
//  }
//
//  public void doTheDishes(){
//      dinnerStrategy.doTheDishes();
//  }

// 2. 파라미터 주입 방식

    public void makeDinner(DinnerStrategy dinnerStrategy){
        dinnerStrategy.makeDinner();
    }

    public void doTheDishes(DinnerStrategy dinnerStrategy){
        dinnerStrategy.doTheDishes();
    }

}

DinnarParty.java

public class DinnerParty {
    public static void main(String[] args) {
        DinnerContext meal = new DinnerContext();
        meal.makeDinner(new NormalDinner());
        meal.doTheDishes(new NormalDinner());
       
       // 출력 결과
       // 배고프네~ 밥먹자~ 구구구~
	   // 밥을 먹엇으면 설거지를 해야지
    }
}

 

자 저녁을 우선 NormalDinner 전략으로 해결했다.

 

그런데 어느날은 빠르게 처리하고 싶었다.

 

그래서 새로운 전략을 수립한다.

FastDinner.java

public class FastDinner implements DinnerStrategy {
    @Override
    public void makeDinner() {
        System.out.println("저녁밥을 빨리 만들자!");
    }

    @Override
    public void doTheDishes() {
        System.out.println("설거지는 빨리 하자!");
    }
}

DinnerParty.java - modified

public class DinnerParty {

    public static void main(String[] args) {
        DinnerContext meal = new DinnerContext();
        meal.makeDinner(new NormalDinner());
        meal.doTheDishes(new NormalDinner());

        meal.makeDinner(new FastDinner());
        meal.doTheDishes(new FastDinner());
    }

}

 

출력 결과 확인하기 클릭

더보기
배고프네~ 밥먹자~ 구구구~
밥을 먹엇으면 설거지를 해야지
저녁밥을 빨리 만들자!
설거지는 빨리 하자!

 

결국 사용하는 클라이언트와는 상관없이 나는 전략만 변경을 하면 된다. 독립적으로 알고리즘을 변경할 수 있는 것이다.

3. 고찰

그러면 우리가 작성한 코드가 전략 패턴에 해당되는지 확인해보자.

 

구조를 다시 살펴보겠다.

 

구조

  • Strategy : 제공하는 모든 알고리즘에 대한 공통의 연산들을 인터페이스로 정의
  • ConcreteStrategy : Strategy 인터페이스를 실제 알고리즘으로 구현
  • Context : ConcreteStrategy 객체를 통해 구성된다. Strategy객체에 대한 참조자를 관리하고, 실제로는 Strategy 서브 클래스의 인스턴스를 갖고 있음으로 구체화한다. 또한, Strategy 객체가 자료에 접근해가는 데 필요한 인터페이스를 정의 

 

1. Strategy에 해당하는 인터페이스는 DinnerStrategy이다.

 

우리가 구현할 알고리즘들이 행할 행동을 여기에 만들어 놓았다.

 

저녁에 키메라는 집에 오면 저녁을 만들고, 설거지를 한다!

 

어떻게 그 행위를 다르게 할 수는 있어도 이를 벗어나지는 않는다.

 

 

2. ConcreteStrategy 인터페이스를 실제로 구현한 것들은 FastDinner, NormalDinner이 있다. 

 

3. Context에 해당하는것은 DinnerContext다. 참조자를 관리하며, 접근할 수 있는 곳이다.

 

DinnerParty.java에서 DinnerContext를 통해서 구현체에 접근을 한다. 

장점

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

단점

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

전략 패턴은 다른 패턴에 비해 이해가 잘 됐다.(쉬웟다는 소리)

 

전략 패턴 하길래 굉장히 심오한 내용을 담고 있을 줄 알았는데 무언가 많이 본 익숙한 구조이기도 하다. 

 

생성자를 주입하는 방식과, 익명 내부 클래스를 사용하는 법, 그리고 지금처럼 파라미터를 주입해서 사용할 수 있으니 그점에 유의해서 사용하면 될 것 같다.