-
Observer Pattern(옵저버 패턴)STUDY/디자인패턴 2025. 2. 16. 22:40
옵저버 패턴이란?
옵저버(관찰자)들이 관찰하고 있는 대상자의 상태가 변화가 있을 때마다 대상자는 직접 목록의 각 관찰자들에게 통지하고, 관찰자들은 알림을 받아 조치를 취하는 행동 디자인 패턴으로, 주로 발행(Publisher) - 구독(Subscriber) 모델을 구현할 때 사용되며 이벤트 기반 프로그래밍에서 많이 사용된다.
- 관찰 대상자 : Subject, Publisher
- 관찰자 : Observer, Subscriber
- 관찰 대상자와 관찰자는 1:N 관계
흐름
- Observer들이 Subject에 등록됨
- Subject의 상태 변경
- Subject가 모든 등록된 Observer들에게 변경을 알림
- 각 Observer는 전달받은 변경 사항에 따라 동작을 수행
구조

- Publisher
출판사는 다른 객체들에 관심 이벤트들을 발행한다. 이러한 이벤트들은 출판사가 상태를 전환하거나 어떤 행동들을 실행할 때 발생한다. 출판사들에는 구독 인프라가 포함되어 있으며, 이 인프라는 현재 구독자들이 리스트를 떠나고 새 구독자들이 리스트에 가입할 수 있도록 한다. 새 이벤트가 발생하면 출판사는 구독자 리스트를 살펴본 후 각 구독자 객체의 구독자 인터페이스에 선언된 알림 메서드를 호출한다.
- Subscriber
구독자 인터페이스로 알림 인터페이스를 선언하며 대부분의 경우 단일 update 메서드로 구성된다. 이 메서드에는 출판사가 업데이트와 함께 어떤 이벤트의 세부 정보들을 전달할 수 있도록 하는 여러 매개변수가 존재할 수 있다.
- Concrete Subscriber
구상 구독자 클래스로 출판사가 보낸 알람들에 대한 응답으로 몇 가지 작업을 수행한다. 모든 구상 클래스는 출판사가 구상 클래스들과 결합하지 않도록 같은 인터페이스를 구현해야 한다.
- Client
출판사 및 구독자 객체들을 별도로 생성한 후 구독자들을 출판사 업데이트에 등록한다.
예시

Publisher
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); } } }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."); } } }Subscribe
public interface EventListener { void update(String eventType, File file); }Concrete Subscribe
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()); } } 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()); } }Client
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(); } } }적용
- 한 객체의 상태가 변경되어 다른 객체들을 변경해야 할 필요성이 생겼을 때, 그리고 실제 객체 집합들을 미리 할 수 없거나 이러한 집합들이 동적으로 변경되는 경우
- 앱의 일부 객체들이 제한된 시간 동안 또는 특정 경우에만 다른 객체들을 관찰해야 하는 경우
장단점
장점
- 개방/폐쇄 원칙. 출판사의 코드를 변경하지 않고도 새 구독자 클래스들을 도입할 수 있다.(출판사 인터페이스가 있는 경우에는 구독자의 클래스들을 변경하지 않고 새 출판사 클래스들을 도입할 수 있다.)
- 런타임에 객체 간의 관계들을 형성할 수 있다.
단점
- 구독자들은 무작위 순서로 알림을 받는다.
실제 사용
- 이벤트 리스너
- Spring 이벤트 시스템
- Pub-Sub 메시징 시스템(Kafka, RabbitMQ)
- GUI 컴포넌트(React, Swing)
'STUDY > 디자인패턴' 카테고리의 다른 글
Strategy Pattern(전략 패턴) (0) 2025.03.03 State Pattern(상태 패턴) (0) 2025.02.16 Memento Pattern(메멘토 패턴) (0) 2025.02.16 Mediator Pattern(중재자 패턴) (0) 2025.02.02 Iterator Pattern(반복자 패턴) (0) 2025.02.02