관리 메뉴

공부 기록장 💻

[디자인패턴] 전략 패턴 (Strategy Pattern) 에 대해 알아보자 본문

# Tech Studies

[디자인패턴] 전략 패턴 (Strategy Pattern) 에 대해 알아보자

dream_for 2023. 6. 12. 10:36

전략 패턴(Strategy Pattern) 이란?

 

 

전략 패턴이란, 실행 중에 알고리즘을 선택할 수 있게 하는 행위 소프트웨어 디자인 패턴 (Behavior Design Pattern) 이다. 특정한 계열의 알고리즘들을 정의하고, 각 알고리즘을 캡슐화 하여 이 알고리즘들을 해당 계열 안에서 상호 교체가 가능하게 만든다.

각 객체들이 할 수 있는 행위(알고리즘) 각각에 대해 전략 클래스를 생성하고, 유사한 행위들을 캡슐화하는 인터페이스를 정의하여, 객체의 행위를 동적으로 바꾸고 싶은 경우 직접 행위를 수정하지 않고 전략을 바꿔줌으로써 행위를 유연하게 확장하는 방법을 말한다.

 

 

여기서 포인트는?

  • 유사한 행위에 대한 알고리즘을 각각 정의하여 캡슐화 한다.
  • 객체의 행위(알고리즘, 전략)를 동적으로 바꿀 수 있는데, 직접 수정하는 것이 아니라 주입을 통해 유연하게 확장한다.

전략 패턴을 구성하는 3가지 요소

  1. Client : 전략 객체를 생성하여 컨텍스트에 주입하는 제 3자 (공급자)
    • 클라이언트는 다양한 전략 중 하나를 선택하여 생성한 후, 컨텍스트에게 주입한다.
  2. Context : 전략 객체를 사용하는 소비자
    • 공통되는 로직이 작성되어 있는 클래스
  3. Strategy : 구체적인 전략의 공통, 추상체
    • Strategy 인터페이스와 각 비즈니스 로직을 담당하는 하위 구현체들을 선언
  4. ConcreteStrategy : 변경되는 구체적인 전략 구현체
    • 새로운 비즈니스 로직이 추가되면, 인터페이스나 다른 구현체 변경 없이 새로 추가하기만 하면 됨

전략 패턴의 개념도 (스프링 입문을 위한 자바 객체 지향의 원리와 이해)

길찾기 예시를 통해 알아보자!

  • 여러 이동 수단을 이용하는 길찾기 최적 경로 프로그램예시 ) 네이버 길찾기

  •  사용자로부터 출발지, 도착지, 그리고 이동 수단을 입력 받아 길찾기를 진행하고 최적 경로를 알려주는 프로그램
  • 기존 코드 if-else 분기 구문으로 이루어진 기존 코드의 단점
    1. 확장의 어려움
      • 새로운 수단 (ex) 자전거, 미래 모빌리티) 을 통한 길찾기를 하고 싶은 경우, 코드가 길어지고 복잡해진다.
    System.out.println("이동 수단을 선택해 주세요.(0. 종료 1. 대중 교통 2. 자동차 3. 도보 4. 자전거 5. 미래 모빌리티)");
    
    else if (userInput==4){
        System.out.println("자전거를 이용한 최적 경로를 계산합니다.");
        //
    }
    else if (userInput==5){
        System.out.println("미래 모빌리티 XXX를 이용한 최적 경로를 계산합니다.");
        //
    }
    
    1. 누락, 변경의 위험성
      • 기존 코드를 수정하는 과정에서 수정하는 위치를 매번 찾아야 하고, 수정하면서 다른 코드에 영향을 미칠 수 있다.
    즉 유지 보수하기 어려운 코드가 생산되었다.
  • public class Main { public static void main(String[] args) { Scanner input = new Scanner(System.in); System.out.println("출발 지점을 입력해 주세요."); String departure = input.next(); System.out.println("도착 지점을 입력해 주세요."); String destination = input.next(); System.out.println("이동 수단을 선택해 주세요.(0. 종료 1. 대중 교통 2. 자동차 3. 도보)"); int userInput = input.nextInt(); if (userInput==1){ System.out.println("대중 교통을 이용한 최적 경로를 계산합니다."); // 대중 교통 기반 최적 경로 계산 } else if (userInput==2){ System.out.println("자동차를 이용한 최적 경로를 계산합니다."); // 자동차를 이용한 최적 경로 계산 } else if (userInput==3){ System.out.println("도보를 이용한 최적 경로를 계산합니다."); // 도보를 이용한 최적 경로 계산 } } }
  • 기존의 분기 코드
    • 전략 Strategy 코드
      • trace() 메서드: 출발지에서 도착지까지의 최적 경로를 계산하는 추상 메소드
      public interface RouteStrategy {
          void trace(LocationInput locationInput);
      }
      
      
      전략 구현체 : PublicTransportStrategy전략 구현체 : CarStrategy전략 구현체 : WalkStrategy전략 구현체 : BicycleStrategy새로운 전략 구현체 : FutureMobilityXXX ?OCP(Open-Closed Principle, 개방 폐쇄 원칙) : 확장에 대해서는 열려 있고, 변경에 대해서는 닫혀 있는 원칙
       
      • public class BicycleStrategy implements RouteStrategy { @Override public void trace(LocationInput locationInput) { System.out.println(locationInput.getDeparture() + "에서 "+locationInput.getDestination()+"까지 자전거를 이용한 최적 경로 입니다.\\n\\n"); // } }
      • public class WalkingStrategy implements RouteStrategy { @Override public void trace(LocationInput locationInput) { System.out.println(locationInput.getDeparture() + "에서 "+locationInput.getDestination()+"까지 도보를 이용한 최적 경로입니다.\\n\\n"); // } }
      • public class CarStrategy implements RouteStrategy { @Override public void trace(LocationInput locationInput) { System.out.println(locationInput.getDeparture() + "에서 "+locationInput.getDestination()+"까지 자동차를 이용한 최적 경로 입니다.\\n\\n"); // } }
      • public class PublicTransportStrategy implements RouteStrategy { @Override public void trace(LocationInput locationInput) { System.out.println(locationInput.getDeparture() + "에서 "+locationInput.getDestination()+"까지 대중 교통을 이용한 최적 경로 입니다.\\n\\n"); // } }
      • 전략 : RouteStrategy 인터페이스
      • Context 코드
        • execute(): 주입받은 RouteStrategy 구현체의 trace() 메서드를 호출
        public class ShortestPathCalculator {
            private LocationInput locationInput;
            private RouteStrategy routeStrategy;
        
            void setLocation(LocationInput locationInput){
                this.locationInput = locationInput;
            }
        
            void setRouteStrategy(RouteStrategy strategy){
                this.routeStrategy = strategy;
            }
        
            void execute(){
                System.out.println("출발 : " + locationInput.getDeparture() + "\\n도착 : " + locationInput.getDestination());
                routeStrategy.trace(locationInput);
            }
        }
        
        
        DIP (Dependency Injection Principle, 의존성 주입 원칙) : 상위 모듈이 하위 모듈에 의존하지 않음
      • 컨텍스트 : ShortestPathCalculator
      • Client 코드
        • 전략 객체를 생성하고 주입하는 공급자
        public class MapUserClient {
        
            public static void main(String[] args) {
                Scanner input = new Scanner(System.in);
                ShortestPathCalculator pathCalculator = new ShortestPathCalculator();
                LocationInput locationInput = new LocationInput();
        
                System.out.println("출발 지점을 입력해 주세요.");  locationInput.setDeparture(input.next());
                System.out.println("도착 지점을 입력해 주세요.");   locationInput.setDestination(input.next());
                pathCalculator.setLocation(locationInput);
        
                while(ifInput){
                    System.out.println("이동 수단을 선택해 주세요.(1. 대중 교통 2. 자동차 3. 도보 4. 자전거)");
                    int userInput = input.nextInt();
                    switch (userInput) {
                        case PUBLIC_TRANSPORTATION:
                            pathCalculator.setRouteStrategy(new PublicTransportStrategy());
                            continue;
                        case AUTO_MOBILE:
                            pathCalculator.setRouteStrategy(new CarStrategy());
                            continue;
                        case WALK:
                            pathCalculator.setRouteStrategy(new WalkingStrategy());
                            continue;
                        case BICYCLE:
                            pathCalculator.setRouteStrategy(new BicycleStrategy());
                    }
                    pathCalculator.execute(); // 실행 
                }
            }
        }
        
        public class LocationInput {
            private String departure;
            private String destination;
        
            void setDeparture(String departure){
                this.departure = departure;
            }
        
            void setDestination(String destination){
                this.destination = destination;
            }
        
            String getDeparture(){return departure;}
            String getDestination(){return destination;}
        }
        
        
        public class ClientCommand {
            public static final int EXIT = 0;
            public static final int PUBLIC_TRANSPORT = 1;
            public static final int CAR = 2;
            public static final int WALK = 3;
            public static final int BICYCLE = 4;
        }
        
        
      • Client : MapUserClient전략 패턴을 적용한 새로운 코드
     

 

전략 패턴은 언제 적용하는 것이 좋을까?

  • 객체 내에서 다른 알고리즘을 사용하고, 런타임 중에 한 알고리즘에서 다른 알고리즘으로 전환이 필요할 때
  • 일부 동작을 실행하는 방식만 다른 유사한 클래스가 많이 있는 경우
  • 패턴을 사용하여, 컨텍스트에서 중요하지 않을 수 있는 알고리즘을 세부 구상체에서 구현하여, 클래스의 비즈니스 로직을 분리하고자 할 때
  • 클래스에 동일한 계열 내의 서로 다른 알고리즘을 전환하는 거대한 덩어리의 조건문이 있는 경우

🧐 전략 패턴의 장점

  • 새로운 전략을 추가해도 기존 코드(Context 코드)를 변경하지 않는다. (OCP)
  • 상속 대신 위임을 사용할 수 있다. (DIP)
  • 실행 중 동적으로 전략의 변경이 가능하다. (런타임 시 전략 변경)
  • 알고리즘의 구현 세부 정보를 분리할 수 있다.

☹️ 전략 패턴의 단점

  • 클라이언트 코드가 구체적인 전략을 알아야 한다.
  • 전략 패턴 도입으로 설계의 복잡도가 증가한다.
    • 한 두 가지의 알고리즘만 있고, 패턴이 거의 변경되지 않는 경우 과도하게 추상화하여 복잡하게 만들 필요가 없다.
  • 최신 모던 프로그래밍 기술을 이용해 익명 함수를 활용하여 다양한 버전의 알고리즘을 구현할 수 있기 때문에, 인터페이스와 클래스로 분리하여 코드를 부풀리지 않는 것이 좋다.

전략 패턴을 한 문장으로 요약해본다면?

클라이언트의 전략을 생성해 전략을 실행할 컨텍스트에 주입하는 패턴!

728x90
반응형
Comments