본문 바로가기
3학년 2학기 학사/소프트웨어 분석 및 설계

[소프트웨어 분석 및 설계] L22. State Pattern, Strategy Pattern

by whiteTommy 2024. 12. 13.
반응형

State Pattern

  • 상태 (state) 패턴이란?
    • 객체의 내부 상태가 변경될 때, 객체 스스로가 상태에 따라 행동을 변경할 수 있도록 하는 행동 패턴
      • 상태(state) : 객체가 가질 수 있는 어떤 조건이나 상황
      • 예) TV 가 켜져 있다면 음량 버튼은 음량을 조절할 수 있지만, 꺼져 있다면 음량은 바뀌지 않음 (티비 전원의 상태에 따라 메소드의 행동이 바뀜)
  • 상태 패턴이 필요한 상황
    • Documnet (문서) 클래스가 있고, Draft (초안), Moderation (검토), Published (출판됨) 의 세 가지 상태를 가진다고 하자
    • publish 메소드는 각 상태에 따라 약간씩 다르게 작동
      • Draft 일 때, 문서를 검토 상태로 이동
      • Moderation 일 때, 문서를 공개하거나 관리자에게만 공개
      • Published 일 때, 아무런 작업을 하지 않음
        public class Document{
            //....
            public void publish(User currentUser){
            	switch(state){
                	case DRAFT : 
                    	state = State.MODERATION;
                        break;
                    case MODERATION :
                    	if ("admin".equals(currentUser.getRole())
                        	state = State.PUBLISHED
                        break;
                    case PUBLISHED:
                    	break;
                }
            }
        }
    • 상태 패턴의 아이디어
      • 객체의 모든 가능한 상태들에 대해서 새 클래스를 만들고 모든 상태별 행동들을 상태 클래스에 추출
    • 상태 패턴의 구조
    • 상태 패턴 예시 코드
      • 노트북 전원 상태에 따른 동작 설계
        • 노트북 전원 ON 상태에서 전원 버튼을 누르면 전원 OFF 상태로 변경
        • 노트북 전원 OFF 상태에서 전원 버튼을 누르면 전원 ON 상태로 변경
        • 노트북 절전 모드 상태에서 전원 버튼을 누르면 전원 ON 상태로 변경
          public interface PowerState {
              void pushPowerButton(LaptopContext context);
          }
          
          public class OnState implements PowerState {
              @Override
              public void pushPowerButton(LaptopContext context) {
                  System.out.println("Laptop power OFF"); // 동작
                  context.changeState(new OffState()); // 상태 전환
              }
          }
          
          public class OffState implements PowerState {
              @Override
              public void pushPowerButton(LaptopContext context) {
                  System.out.println("Laptop power ON"); // 동작
                  context.changeState(new OnState()); // 상태 전환
              }
          }
          
          public class SavingState implements PowerState {
              @Override
              public void pushPowerButton(LaptopContext context) {
                  System.out.println("Laptop power on"); // 동작
                  context.changeState(new OnState()); // 상태 전환
              }
          }
          
          public class LaptopContext {
              PowerState powerState;
          
              public LaptopContext() {
                  this.powerState = new OffState(); // 초기 상태 설정
              }
          
              void changeState(PowerState state) {
                  this.powerState = state; // 상태 변경
              }
          
              void pushPowerButton() {
                  this.powerState.pushPowerButton(this); // 현재 상태에 따라 동작 수행
              }
          
              void setSavingState() {
                  System.out.println("Laptop saving mode"); // 절전 모드로 전환
                  this.changeState(new SavingState());
              }
          
              void printCurrentState() {
                  System.out.println(this.powerState.getClass().getName()); // 현재 상태 출력
              }
          }
          
          public class Client {
              public static void main(String[] args) {
                  LaptopContext laptop = new LaptopContext(); // Context 객체 생성
          
                  laptop.pushPowerButton(); // OFF -> ON
                  laptop.printCurrentState(); // OnState 출력
          
                  laptop.setSavingState(); // ON -> SAVING
                  laptop.printCurrentState(); // SavingState 출력
          
                  laptop.pushPowerButton(); // SAVING -> ON
                  laptop.printCurrentState(); // OnState 출력
          
                  laptop.pushPowerButton(); // ON -> OFF
                  laptop.printCurrentState(); // OffState 출력
              }
          }


          • 일반적으로 상태를 매번 다른 인스턴스로 하지 않아도 됨 => 싱글톤으로 코드 개선
            public interface PowerState {
                void pushPowerButton(LaptopContext context);
            }
            
            public class OnState implements PowerState {
                private static final OnState INSTANCE = new OnState(); // 싱글톤 인스턴스 생성
            
                private OnState() {} // private 생성자
            
                public static OnState getInstance() {
                    return INSTANCE; // 싱글톤 인스턴스 반환
                }
            
                @Override
                public void pushPowerButton(LaptopContext context) {
                    System.out.println("Laptop power OFF");
                    context.changeState(OffState.getInstance()); // 싱글톤 상태로 전환
                }
            }
            
            public class OffState implements PowerState {
                private static final OffState INSTANCE = new OffState(); // 싱글톤 인스턴스 생성
            
                private OffState() {} // private 생성자
            
                public static OffState getInstance() {
                    return INSTANCE; // 싱글톤 인스턴스 반환
                }
            
                @Override
                public void pushPowerButton(LaptopContext context) {
                    System.out.println("Laptop power ON");
                    context.changeState(OnState.getInstance()); // 싱글톤 상태로 전환
                }
            }
            
            public class SavingState implements PowerState {
                private static final SavingState INSTANCE = new SavingState(); // 싱글톤 인스턴스 생성
            
                private SavingState() {} // private 생성자
            
                public static SavingState getInstance() {
                    return INSTANCE; // 싱글톤 인스턴스 반환
                }
            
                @Override
                public void pushPowerButton(LaptopContext context) {
                    System.out.println("Laptop power on");
                    context.changeState(OnState.getInstance()); // 싱글톤 상태로 전환
                }
            }
            
            public class LaptopContext {
                private PowerState powerState;
            
                public LaptopContext() {
                    this.powerState = OffState.getInstance(); // 초기 상태 설정 (싱글톤 사용)
                }
            
                void changeState(PowerState state) {
                    this.powerState = state; // 상태 변경
                }
            
                void pushPowerButton() {
                    this.powerState.pushPowerButton(this); // 현재 상태에 따라 동작 수행
                }
            
                void setSavingState() {
                    System.out.println("Laptop saving mode");
                    this.changeState(SavingState.getInstance()); // 싱글톤 상태로 전환
                }
            
                void printCurrentState() {
                    System.out.println(this.powerState.getClass().getName()); // 현재 상태 출력
                }
            }
            
            public class Client {
                public static void main(String[] args) {
                    LaptopContext laptop = new LaptopContext();
            
                    laptop.pushPowerButton(); // OFF -> ON
                    laptop.printCurrentState(); // OnState 출력
            
                    laptop.setSavingState(); // ON -> SAVING
                    laptop.printCurrentState(); // SavingState 출력
            
                    laptop.pushPowerButton(); // SAVING -> ON
                    laptop.printCurrentState(); // OnState 출력
            
                    laptop.pushPowerButton(); // ON -> OFF
                    laptop.printCurrentState(); // OffState 출력
                }
            }


    • 장점
      • SRP 준수
        • 상태에 따른 동작을 개별 클래스로 옮겨서 관리할 수 있음
      • OCP 준수
        • 기존 상태 클래스나 컨텍스트를 변경하지 않고 새 상태 도입 가능
      • 상태와 관련된 모든 동작을 각각의 상태 클래스에 분산시켜 로직의 복잡도를 줄일 수 있음
    • 단점
      • 상태 별로 클래스를 생성하므로 관리해야 하는 클래스 수 증가
      • 객체에 적용할 상태가 몇가지 밖에 없거나 거의 상태 변경이 이루어지지 않는 경우 패턴 적용이 과할 수 있음

 

 

Strategy Pattern

  • 전략 (strategy) 패턴이란?
    • 실행 중에 알고리즘 전략을 선택하여 객체 동작을 실시간으로 바뀌도록 할 수 있게 하는 행동 패턴
      • 전략 : 일종의 알고리즘 또는 기능이나 동작
  • 전략 패턴이 필요한 상황
    • 내비게이션 앱에서 목적지까지 빠른 경로를 찾아주는 기능을 설계한다고 하자
      • 첫 번째 버전에서는 도로로 된 경로만을 찾을 수 있도록 하면, 자동차 사용자는 만족하겠지만, 차가 없는 사람은 만족스럽지 않음
      • 도보, 대중교통, 자전거 등의 경로 옵션을 제공하는 형태로 확장
      • 새로운 경로 구축 알고리즘을 추가할 때마다 네비게이터 클래스가 복잡해짐
  • 전략 패턴의 아이디어
    • 특정 작업은 다양한 방식으로 수행하는 클래스를 선택한 후 모든 알고리즘을 strategy 라는 별도의 클래스로 추출
  • 전략 패턴의 구조
  • 전략 패턴 예시 코드
    public interface PaymentStrategy {
        void pay(int amount); // 결제 금액을 처리하는 메서드
    }
    
    public class MasterCardStrategy implements PaymentStrategy {
        private String name;
        private String cardNumber;
        private String cvv;
    
        public MasterCardStrategy(String name, String cardNumber, String cvv) {
            this.name = name;
            this.cardNumber = cardNumber;
            this.cvv = cvv;
        }
    
        @Override
        public void pay(int amount) {
            System.out.println(amount + " Won paid using MasterCard");
        }
    }
    
    public class MobilePayStrategy implements PaymentStrategy {
        private String email;
        private String password;
    
        public MobilePayStrategy(String email, String password) {
            this.email = email;
            this.password = password;
        }
    
        @Override
        public void pay(int amount) {
            System.out.println(amount + " Won paid using MobilePay");
        }
    }
    
    public class ShoppingCart {
        private List<Item> items;
        private PaymentStrategy paymentStrategy;
    
        public ShoppingCart() {
            this.items = new ArrayList<>();
        }
    
        public void addItem(Item item) {
            this.items.add(item); // 장바구니에 아이템 추가
        }
    
        public int getTotalPrice() {
            int total = 0;
            for (Item item : items) {
                total += item.getPrice(); // 총합 계산
            }
            return total;
        }
    
        public void setPaymentStrategy(PaymentStrategy paymentStrategy) {
            this.paymentStrategy = paymentStrategy; // 결제 전략 설정
        }
    
        public void pay() {
            int amount = getTotalPrice(); // 총 금액 계산
            this.paymentStrategy.pay(amount); // 선택된 결제 전략 실행
        }
    }
    
    public class Item {
        private String name;
        private int price;
    
        public Item(String name, int price) {
            this.name = name;
            this.price = price;
        }
    
        public int getPrice() {
            return price; // 아이템 가격 반환
        }
    }
    
    public class Client {
        public static void main(String[] args) {
            ShoppingCart cart = new ShoppingCart();
            cart.addItem(new Item("MacBook", 120000));
            cart.addItem(new Item("iPhone", 20000));
    
            cart.setPaymentStrategy(new MasterCardStrategy("John", "1234", "111"));
            cart.pay(); // MasterCard 결제
    
            cart.setPaymentStrategy(new MobilePayStrategy("test@example.com", "12345"));
            cart.pay(); // MobilePay 결제
        }
    }
  • JAVA 에서 전략 패턴 사용 예시
    • Comparator : 비교 전략을 런타임에 정의하고 변경할 수 있음
      import java.util.*;
      
      public class StrategyInJava {
          public static void main(String[] args) {
              List<Integer> numbers = new ArrayList<>();
              numbers.add(3);
              numbers.add(2);
              numbers.add(1);
      
              // 정렬 전략 설정 
              Collections.sort(numbers, new Comparator<Integer>() {
                  @Override
                  public int compare(Integer o1, Integer o2) {
                      return o1 - o2; 
                  }
              });
      
              System.out.println(numbers); 
          }
      }
  • 장점
    • 런타임에 객체 내부에서 사용되는 알고리즘을 교환할 수 있음
    • OCP 준수
      • 새로운 알고리즘을 추가하더라도 기존 코드에 영향이 없음
    • SRP 준수
      • 전략 클래스는 단 하나의 알고리즘을 가짐
    • 다른 컨텍스트에서 전략을 재사용하기 편함
  • 단점
    • 알고리즘이 몇 개밖에 되지 않고 거의 변하지 않는다면 사용이 과함
    • 클라이언트들은 적절한 전략을 선택할 수 있도록 전략 간의 차이점을 알고 있어야 함

 

State v.s. Strategy

  • 유사점
    • 클래스 다이어그램이 거의 유사하고 사용법이 비슷함
    • 둘 다 합성 (composition) 을 통해 해결함
  • 차이점
    • 초점에 있어서의 차이
      • 상태 패턴은 객체의 상태에 따른 행동 변화에 중점
      • 전략 패턴은 알고리즘의 동적인 교체에 중점
    • 적용 시나리오에 있어서 차이
      • 상태 패턴은 객체가 여러 상태를 가지고 있고, 상태에 따라 행동이 달라야 할 때 사용
      • 전략 패턴은 알고리즘이 독립적으로 정의하고 교체해야 할 때 사용

 

 

 

 

 

 

 

 

반응형