반응형
Mediator Pattern
- 중재자 (mediator) 패턴이란?
- 객체 간의 직접 통신을 제한하고 중재자 객체를 통해서만 협력하도록 하는 행동 패턴
- 중재자 : 객체 간의 통신을 관리하고 매개체 역할을 함
- 객체 간의 결합을 낮추고 유연성을 확보
- 객체 간의 직접 통신을 제한하고 중재자 객체를 통해서만 협력하도록 하는 행동 패턴
- 중재자 패턴이 필요한 상황
- 프로필을 만들고 편집하기 위한 대화상자 (dialog) 에서 다양한 요소들이 상호 작용할 수 있음
- 각 객체들이 서로 필요한 객체들을 참조
- 한 클래스가 수정되면 연관된 클래스들의 수정이 발생할 수 있음
- 프로필을 만들고 편집하기 위한 대화상자 (dialog) 에서 다양한 요소들이 상호 작용할 수 있음
- 중재자 패턴의 아이디어
- 각 객체 간의 연결을 느슨하게 만들어야 함
- 객체 간의 직접적인 통신은 중단하고, 이러한 호출 대신 처리할 수 있는 중재제 객체를 두어서 간접적으로 협력
- M:N 관계를 M:1 로 전환
- 각 객체 간의 연결을 느슨하게 만들어야 함
- 중재자 패턴의 구조
- 중재자 패턴의 코드 예시
public interface Mediator { void sendMessage(Component sender, String message); // 메시지 전달 메서드 void addUser(Component user); // 사용자 추가 메서드 } public class ConcreteMediator implements Mediator { private List<Component> users; // 사용자 목록 public ConcreteMediator() { this.users = new ArrayList<>(); // 사용자 목록 초기화 } @Override public void addUser(Component user) { this.users.add(user); // 사용자 추가 } @Override public void sendMessage(Component sender, String message) { for (Component receiver : users) { if (sender != receiver) { // 보낸 사람(sender)을 제외하고 메시지 전송 receiver.receive(message); } } } } public class Component { private Mediator mediator; // Mediator 참조 private String name; // 사용자 이름 public Component(Mediator mediator, String name) { this.mediator = mediator; this.name = name; } public void receive(String message) { System.out.println("[수신] > " + this.name + " : " + message); // 메시지 수신 출력 } public void send(String message) { System.out.println("[발신] > " + this.name + " : " + message); // 메시지 발신 출력 mediator.sendMessage(this, message); // Mediator에 메시지 전달 요청 } } public class Main { public static void main(String[] args) { Mediator mediator = new ConcreteMediator(); // ConcreteMediator 생성 // 사용자 생성 Component user1 = new Component(mediator, "A"); Component user2 = new Component(mediator, "B"); Component user3 = new Component(mediator, "C"); // Mediator에 사용자 등록 mediator.addUser(user1); mediator.addUser(user2); mediator.addUser(user3); // 메시지 전송 user1.send("Hello, World!"); user2.send("Who are you?"); } }
- 장점
- 다양한 컴포넌트간의 통신을 한 곳으로 추출하여 코드를 이해하고 유지관리하기 쉽게 만듬
- 프로그램의 다양한 컴포넌트 간의 결합도를 줄일 수 있음
- 개별 컴포넌트들을 더 쉽게 재사용할 수 있음
- 실제 컴포넌트들을 변경하지 않아도 새로운 중재자를 도입할 수 있음
- 단점
- 중재자는 god object 로 발전할 수도 있음
- 퍼사드 패턴과의 차이점
- 퍼사드 패턴은 객체들의 하위 시스템에 대한 단순화된 인터페이스를 정의하지만 새로운 기능을 도입하지는 않음
- 하위 객체가 퍼사드의 존재를 인지하지 못한다.
- 중재자는 시스템 컴포넌트 간의 통신을 중앙 집중화 함
- 퍼사드 패턴과 공통점
- God object 가 될 수 있다.
Memento Pattern
- 객체의 구현 세부 사항을 공개하지 않으면서 해당 객체의 이전 상태 값을 저장하고 복원할 수 있게 해주는 행동 패턴
- 스냅샷이라고도 불림
- 메멘토 패턴이 필요한 상황
- 텍스트 편집기에서 실행 취소를 구현하려고 할 때, 특정 작업을 수행하기 전에 모든 객체의 상태를 기록하고 실행 취소하면 기록에서 가장 최신 스냅샷을 가져와 복원
- 이를 위해서 객체의 모든 필드를 살펴본 후 해당 값들을 복사해야 함
- 그러나 대부분의 실제 객체들은 모두 중요한 데이터를 비공해함 (캡슐화)
- 공개된 필드라도 할 지라도, 객체 일부가 수정되면 복사를 맡은 클래스들 역시 변경이 되어야 함
- 메멘토 패턴의 아이디어
- 상태 스냅샷들의 생성을 해당 상태의 실제 소유자인 originator 객체에 위임하고 memento 라는 특수 객체에 상태의 복사본을 저장
- 메멘토의 내용에는 메멘토를 생성한 객체를 제외한 다른 어떤 객체에도 접근할 수 없음
- 상태 스냅샷들의 생성을 해당 상태의 실제 소유자인 originator 객체에 위임하고 memento 라는 특수 객체에 상태의 복사본을 저장
- 메멘토 패턴의 구조 - 중첩 클래스에 기반
public class Editor { private String text; // 텍스트 private int posX, posY; // 커서 위치 // 현재 상태를 저장하는 Snapshot 생성 public Snapshot create() { System.out.println("Create"); return new Snapshot(); } // 이전 상태로 복원 public void restore(Snapshot snapshot) { System.out.println("Restore"); this.text = snapshot.getText(); this.posX = snapshot.getPosX(); this.posY = snapshot.getPosY(); } // getters and setters // Memento 내부 클래스 final class Snapshot { final String text; final int posX, posY; // 현재 상태를 Snapshot으로 저장 Snapshot(String text, int posX, int posY) { this.text = text; this.posX = posX; this.posY = posY; } // getters } } public class Command { private Stack<Editor.Snapshot> stack; // 상태를 저장하는 스택 public Command() { this.stack = new Stack<>(); } // 상태 백업 public void makeBackup(Editor origin) { Editor.Snapshot memento = origin.create(); stack.push(memento); } // 상태 복원 public Editor undo(Editor editor) { if (!stack.isEmpty()) { Editor.Snapshot memento = stack.pop(); // 최근 상태 가져오기 editor.restore(memento); // 복원 } return editor; } } public class Client { public static void main(String[] args) { Editor editor = new Editor(); Command care = new Command(); // 상태 1 저장 editor.setText("design pattern 1 - Memento"); editor.setPos(10, 24); System.out.println(editor.getState()); care.makeBackup(editor); // 상태 2 저장 editor.setText("design pattern 2 - Iterator"); editor.setPos(22, 24); System.out.println(editor.getState()); care.makeBackup(editor); // 상태 3 저장 editor.setText("design pattern 3 - Prototype"); editor.setPos(44, 24); System.out.println(editor.getState()); care.makeBackup(editor); // 상태 복원 System.out.println("\n=== 복구 ===\n"); Editor obj = care.undo(editor); // 상태 3 -> 상태 2 System.out.println(obj.getState()); obj = care.undo(editor); // 상태 2 -> 상태 1 System.out.println(obj.getState()); obj = care.undo(editor); // 상태 1 (더 이상 복구할 상태 없음) System.out.println(obj.getState()); } }
- 장점
- 캡슐화를 위반하지 않고 객체의 상태 스냅샷을 생성할 수 있음
- 케어테이커가 오리지네이터의 상태 기록을 유지하도록 하여 오리지네이터의 코드를 단순화할 수 있음
- 단점
- SRP 위배
- 클라이언트의 메멘토를 너무 자주 생성하면 메모리 사용이 늘어남
- 케어테이커에 오래된 메멘토는 삭제하고 관리하는 역할이 부여될 수 있음
Observer Pattern
- 옵저버 패턴이란?
- 옵저버들이 관찰하고 있는 대상의 상태 변화가 있을 때마다 대상자는 각 관찰자에게 통지하고 관찰자들은 알림을 받아 조치를 취하는 행동 패턴
- 발행-구독 모델이라고도 함
- 옵저버들이 관찰하고 있는 대상의 상태 변화가 있을 때마다 대상자는 각 관찰자에게 통지하고 관찰자들은 알림을 받아 조치를 취하는 행동 패턴
- 옵저버 패턴의 구조
- 옵저버 패턴의 흐름
- 한 개의 관찰 대상자와 여러 개의 관찰자로 1:N 관계로 구성
- 관찰 대상자의 상태가 바뀌면 변경사항을 관찰자에게 통보
- 통보를 받은 관찰자는 적절하게 필요에 맞추어 업데이트
- 언제든지 구독 추가/취소가 가능
- 옵저버 패턴 코드 예시 - 날씨 API 로부터 온습도 알림받기
interface Subscriber { void display(WeatherAPI api); } class KoreanUser implements Subscriber { String name; KoreanUser(String name) { this.name = name; } @Override public void display(WeatherAPI api) { System.out.printf("%s님의 현재 날씨 상태를 조회함 : %.2f°C %.2fg/m³ %.2fhPa\n", name, api.temp, api.humidity, api.pressure); } } interface Publisher { void register(Subscriber subscriber); // 구독자 등록 void remove(Subscriber subscriber); // 구독자 제거 void notify(); // 구독자들에게 알림 } class WeatherAPI implements Publisher { float temp; // 온도 float humidity; // 습도 float pressure; // 기압 List<Subscriber> subscribers = new ArrayList<>(); // 구독자 리스트 @Override public void register(Subscriber subscriber) { subscribers.add(subscriber); // 구독자 등록 } @Override public void remove(Subscriber subscriber) { subscribers.remove(subscriber); // 구독자 제거 } @Override public void notify() { for (Subscriber subscriber : subscribers) { subscriber.display(this); // 모든 구독자에게 상태 알림 } } void measurementsChanged() { // 랜덤 데이터로 날씨 상태 변경 temp = new Random().nextFloat() * 100; humidity = new Random().nextFloat() * 100; pressure = new Random().nextFloat() * 100; notify(); // 상태가 변경되었음을 알림 } } public class Main { public static void main(String[] args) { WeatherAPI api = new WeatherAPI(); // WeatherAPI(Publisher) 생성 // 구독자(Subscriber) 생성 및 등록 api.register(new KoreanUser("홍길동")); api.register(new KoreanUser("임꺽정")); api.register(new KoreanUser("세종대왕")); // 상태 변경 및 알림 전송 api.measurementsChanged(); // 구독자들에게 새로운 날씨 데이터 전달 } }
- 장점
- 관찰 대상자의 상태 변경을 주기적으로 조회하지 않고 자동으로 감지
- Publisher 의 코드를 변경하지 않고도 새 구독자 클래스를 도입
- 런타임 시점에 발행자와 구독 알림 관계를 맺음
- 상태 변경하는 객체와 변경을 감지하는 객체의 관계를 느슨하게 유지
- 단점
- 구독자는 알림 순서를 제어할 수 없고, 무작위로 통보만 받음
- 다수의 observer 객체 등록 이후, 사용하지 않는 observer 를 해지하지 않는다면 memory 낭비가 발생
반응형
'3학년 2학기 학사 > 소프트웨어 분석 및 설계' 카테고리의 다른 글
[소프트웨어 분석 및 설계] L24. Template Method Pattern, Visitor Pattern (2) | 2024.12.13 |
---|---|
[소프트웨어 분석 및 설계] L22. State Pattern, Strategy Pattern (1) | 2024.12.13 |
[소프트웨어 분석 및 설계] L21. Interpreter Pattern, Iterator Pattern (1) | 2024.12.13 |
[소프트웨어 분석 및 설계] L19. Facade Pattern, Flyweight Pattern, Proxy Pattern (0) | 2024.12.13 |
[소프트웨어 분석 및 설계] L18. Composite Pattern, Decorator Pattern (0) | 2024.12.13 |