ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Observer Pattern(옵저버 패턴)
    STUDY/디자인패턴 2025. 2. 16. 22:40

    옵저버 패턴이란?

    옵저버(관찰자)들이 관찰하고 있는 대상자의 상태가 변화가 있을 때마다 대상자는 직접 목록의 각 관찰자들에게 통지하고, 관찰자들은 알림을 받아 조치를 취하는 행동 디자인 패턴으로, 주로 발행(Publisher) - 구독(Subscriber) 모델을 구현할 때 사용되며 이벤트 기반 프로그래밍에서 많이 사용된다.

     

    • 관찰 대상자 : Subject, Publisher 
    • 관찰자 : Observer, Subscriber
    • 관찰 대상자와 관찰자는 1:N 관계

     

    흐름

    1. Observer들이 Subject에 등록됨
    2. Subject의 상태 변경
    3. Subject가 모든 등록된 Observer들에게 변경을 알림
    4. 각 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();
            }
        }
    }

     

     

    적용

    1. 한 객체의 상태가 변경되어 다른 객체들을 변경해야 할 필요성이 생겼을 때, 그리고 실제 객체 집합들을 미리 할 수 없거나 이러한 집합들이 동적으로 변경되는 경우
    2. 앱의 일부 객체들이 제한된 시간 동안 또는 특정 경우에만 다른 객체들을 관찰해야 하는 경우

     

    장단점

    장점

    • 개방/폐쇄 원칙. 출판사의 코드를 변경하지 않고도 새 구독자 클래스들을 도입할 수 있다.(출판사 인터페이스가 있는 경우에는 구독자의 클래스들을 변경하지 않고 새 출판사 클래스들을 도입할 수 있다.)
    • 런타임에 객체 간의 관계들을 형성할 수 있다.

     

    단점

    • 구독자들은 무작위 순서로 알림을 받는다.

     

    실제 사용

    • 이벤트 리스너
    • 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
Designed by Tistory.