반응형
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 준수
- 기존 상태 클래스나 컨텍스트를 변경하지 않고 새 상태 도입 가능
- 상태와 관련된 모든 동작을 각각의 상태 클래스에 분산시켜 로직의 복잡도를 줄일 수 있음
- SRP 준수
- 단점
- 상태 별로 클래스를 생성하므로 관리해야 하는 클래스 수 증가
- 객체에 적용할 상태가 몇가지 밖에 없거나 거의 상태 변경이 이루어지지 않는 경우 패턴 적용이 과할 수 있음
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); } }
- Comparator : 비교 전략을 런타임에 정의하고 변경할 수 있음
- 장점
- 런타임에 객체 내부에서 사용되는 알고리즘을 교환할 수 있음
- OCP 준수
- 새로운 알고리즘을 추가하더라도 기존 코드에 영향이 없음
- SRP 준수
- 전략 클래스는 단 하나의 알고리즘을 가짐
- 다른 컨텍스트에서 전략을 재사용하기 편함
- 단점
- 알고리즘이 몇 개밖에 되지 않고 거의 변하지 않는다면 사용이 과함
- 클라이언트들은 적절한 전략을 선택할 수 있도록 전략 간의 차이점을 알고 있어야 함
State v.s. Strategy
- 유사점
- 클래스 다이어그램이 거의 유사하고 사용법이 비슷함
- 둘 다 합성 (composition) 을 통해 해결함
- 차이점
- 초점에 있어서의 차이
- 상태 패턴은 객체의 상태에 따른 행동 변화에 중점
- 전략 패턴은 알고리즘의 동적인 교체에 중점
- 적용 시나리오에 있어서 차이
- 상태 패턴은 객체가 여러 상태를 가지고 있고, 상태에 따라 행동이 달라야 할 때 사용
- 전략 패턴은 알고리즘이 독립적으로 정의하고 교체해야 할 때 사용
- 초점에 있어서의 차이
반응형