일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- Collections
- Eclipse
- 파이썬
- 큐
- tcp
- Pattern
- 로버트마틴
- 프로그래머스
- 디자인패턴
- Spring
- design-pattern
- 스택
- 백준
- JavaScript
- 람다 칼큘러스
- 함수형 프로그래밍
- javscript
- DesignPattern
- exception
- Collection
- solid
- JDBC
- 겨울카카오인턴
- Python
- Rails
- lambda calculus
- Java
- Network
- functional programming
- 자바
- Today
- Total
개발자 노트
Observer Pattern 본문
참고 자료
Intent
분류
- behavioral design pattern
- 그룹 내 object간 어떻게 소통할 지에 대해 관한 패턴
정의
- (guru) 여러 객체들이 관찰하고 있는 객체에 발생하는 이벤트를 알리기 위해 subscription 메커니즘을 정의하도록 하는 패턴
- (wiki) 객체의 상태 변화를 관찰하는 관찰자들, 즉 옵저버들의 목록을 객체에 등록하여 상태 변화가 있을 때마다 메서드 등을 통해 객체가 직접 목록의 각 옵저버에게 통지하도록 하는 디자인 패턴이다
Problem
상황 - 이벤트 알기
Customer
,Store
두 타입의 Object가 존재합니다.- Customer는 Store에서 곧 판매될 iPhone 최신형 모델에 관심있습니다.
문제
- 어떻게 해야 매장에 최신형 아이폰이 나오자마자 고객이 물건을 살 수 있을까요?
안 좋은 방법
[방법 1] 고객이 매일 방문
- 최신형 아이폰이 매장에 출시되었는지 알기 위해 고객이 매일매일 매장을 방문합니다.
단점
- 고객은 출시 되지 않았음에도 매장에 찾아갈 수 있으므로 쓸데없는 동선이 생깁니다. 다시말해서 Customer가 고생한다는거죠
[방법 2] Store가 모든 고객에게 알려주기
- 새로운 제품이 출시될 때마다 모든 고객에게 emails을 보내는 것입니다.
- 이 방법은 [방법 1]처럼 고객이 동선을 낭비하는 일이 없어집니다.
단점
- 새로운 제품에 관심이 없더라도 고객은 알림을 받아야 하므로 화나게 됩니다.
- 매장의 자원이 낭비됩니다.
프로그래밍 관점에서 문제는 다음 2가지로 요약됩니다.
고객이 새로운 제품이 출시되었는지 확인하느라 자원을 낭비하는 일이 없어야하며,
매장이 관심없는 고객에게 보내느라 자원을 낭비하는 일이 없어야 합니다.
어떻게 하면 이 상충되어보이는 문제를 해결할 수 있을까요?
해결책
방법
- 매장에서 새로운 아이폰 출시에 관심있는 고객에게만 emails를 보내주면 됩니다!
이 방법을 설명하기 위해 다음 용어를 소개시켜드리겠습니다.
용어
1. publisher
흥미있는 상태를 가지는 객체로 subject라고 부르나, observer pattern에선 이 객체가 다른 objects들에게 자신의 상태를 알리므로 publisher라고 부릅니다.
2. subscribers
publisher's 상태 변화를 추적(track,observe)하고 싶은 객체를 subscribers라고 부릅니다.
Observer Pattern 적용
- subscriber가 publisher로부터 발생하는 이벤트를 구독하고, 구독취소할 수 있도록 publisher class에 subscription mechanism을 추가하도록 합니다. (ex: addSubscriber, remoeSUbsciber....)
- publisher에서 중요한 이벤트 발생을 전달할 수 있도록 subscribers의 특별한 notification method를 부르도록 합니다.
- 다음으로, publisher가 concrete한 subscriber에게 커플링되지 않도록 subscriber가 동일한 Interface를 구현하도록 합니다.
- 이 인터페이스엔 어떤 이벤트가 발생했는지 알려주기 위해 context data를 넘겨줘야 합니다. 그래서 이 context data를 넘겨줄 수 있는 notification method를 선언합니다.
- 추가적으로 여러 publisher에게 subscriber를 등록하고 싶다면, detail publisher에 의존하지 않도록 publisher를 interface로 선언하도록 합니다.
[고객 - 매장] 문제에 적용
IPhone 출시에 관심있는 고객만 매장에서 등록합니다.
그리고 IPhone이 매장에서 출시되었을 때, 매장에서 구독한 고객에게 알려주는 형태가 됩니다.
구조
1. Publisher
다른 object에게 관심있는 event를 발행(issue)합니다.
이 이벤트는 다음 두 상황에서 발생합니다.
- publisher의 상태가 변경되거나
- 특정 behaviors가 실행될 때
publisher는 subscription에 대한 infrastructure를 가지고 있습니다. (정의된 Mechanism)
- subscriber를 저장하는 container
- subscriber를 추가하는 메서드
- subscriber를 삭제하는 메서드
이벤트가 발생하면 Subscriber들을 일일이 확인하며(goes over) subscriber에 있는 notification method를 실행합니다.
2. Subscriber (interface)
- notification interface를 선언합니다.
- 보통 single update method를 지닙니다.
- 이 method는 contextural parameters를 받습니다. 이를 통해 publisher가 event detail을 넘겨줍니다.
3. Concrete Subscriber
- publisher에 의해 실행된 notification에 반응하여 특정 액션을 수행합니다.
4. Client
- publisher와 subscriber를 각각 생성하고 publisher에 subscriber를 등록합니다.
예제
구조
이 예제는 publisher의 subscription Inftrastructure를 EventManager로 분리하였습니다. 따라서 훨씬 좋은 구조라 보기에 직접 예제를 작성하지 않고 소개시켜드립니다.
1. Publisher -Basic publisher
package refactoring_guru.observer.example.publisher;
import refactoring_guru.observer.example.listeners.EventListener;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class EventManager {
Map<String, List<EventListener>> listeners = new HashMap<>();
public EventManager(String... operations) {
for (String operation : operations) {
this.listeners.put(operation, new ArrayList<>());
}
}
public void subscribe(String eventType, EventListener listener) {
List<EventListener> users = listeners.get(eventType);
users.add(listener);
}
public void unsubscribe(String eventType, EventListener listener) {
List<EventListener> users = listeners.get(eventType);
users.remove(listener);
}
public void notify(String eventType, File file) {
List<EventListener> users = listeners.get(eventType);
for (EventListener listener : users) {
listener.update(eventType, file);
}
}
}
2. Editor - Concrete publisher, tracked by other objects
package refactoring_guru.observer.example.editor;
import refactoring_guru.observer.example.publisher.EventManager;
import java.io.File;
public class Editor {
public EventManager events;
private File file;
public Editor() {
this.events = new EventManager("open", "save");
}
public void openFile(String filePath) {
this.file = new File(filePath);
events.notify("open", file);
}
public void saveFile() throws Exception {
if (this.file != null) {
events.notify("save", file);
} else {
throw new Exception("Please open a file first.");
}
}
}
3. Listeners
package refactoring_guru.observer.example.listeners;
import java.io.File;
public interface EventListener {
void update(String eventType, File file);
}
4. EmailNotificationListener
package refactoring_guru.observer.example.listeners;
import java.io.File;
public class EmailNotificationListener implements EventListener {
private String email;
public EmailNotificationListener(String email) {
this.email = email;
}
@Override
public void update(String eventType, File file) {
System.out.println("Email to " + email + ": Someone has performed " + eventType + " operation with the following file: " + file.getName());
}
}
5. LogOpenListener
package refactoring_guru.observer.example.listeners;
import java.io.File;
public class LogOpenListener implements EventListener {
private File log;
public LogOpenListener(String fileName) {
this.log = new File(fileName);
}
@Override
public void update(String eventType, File file) {
System.out.println("Save to log " + log + ": Someone has performed " + eventType + " operation with the following file: " + file.getName());
}
}
6. Demo
package refactoring_guru.observer.example;
import refactoring_guru.observer.example.editor.Editor;
import refactoring_guru.observer.example.listeners.EmailNotificationListener;
import refactoring_guru.observer.example.listeners.LogOpenListener;
public class Demo {
public static void main(String[] args) {
Editor editor = new Editor();
editor.events.subscribe("open", new LogOpenListener("/path/to/log/file.txt"));
editor.events.subscribe("save", new EmailNotificationListener("admin@example.com"));
try {
editor.openFile("test.txt");
editor.saveFile();
} catch (Exception e) {
e.printStackTrace();
}
}
}
'디자인패턴 > Behavioral Design Pattern' 카테고리의 다른 글
Strategy Pattern (0) | 2020.10.18 |
---|---|
State Pattern (0) | 2020.10.18 |
Memento Pattern (0) | 2020.10.12 |
Mediator Pattern (0) | 2020.10.12 |
Iterator Pattern (0) | 2020.10.12 |