inblog logo
|
DailyCoding
    Spring Boot

    13-7. 옵저버 패턴 (Observer Pattern)

    박은서's avatar
    박은서
    Feb 24, 2026
    13-7. 옵저버 패턴 (Observer Pattern)
    Contents
    1. 옵저버 패턴 (Observer Pattern)1️⃣ 옵저버 패턴이란?2️⃣ Java에서의 역할 구조3️⃣ 구조 다이어그램 (개념)4️⃣ 코드 예시 (정석 구현)5️⃣ Java에서 중요한 포인트6️⃣ Java 표준 라이브러리의 옵저버7️⃣ 실무에서의 대표 사용 예8️⃣ 옵저버 vs Pub-Sub 차이 (Java 관점)9️⃣ 단점2. 실습(ex07)1️⃣ polling (패턴X 옵저버가 뭔지)2️⃣ push (옵저버 패턴)

    1. 옵저버 패턴 (Observer Pattern)

    1️⃣ 옵저버 패턴이란?

    1) 개념

    한 객체(Subject)의 상태(state)가 변경되면, 해당 객체에 등록된 여러 Observer 객체들에게 자동으로 통지(notify)하는 구조
    ⚠️ Java에서는 인터페이스 기반 설계로 구현하는 것이 일반적

    2️⃣ Java에서의 역할 구조

    1) Subject (관찰 대상)

    • Observer 목록을 관리
    • register(), remove(), notifyObservers() 제공

    2) Observer (관찰자)

    • update() 메서드를 정의
    • Subject가 상태 변경 시 호출됨

    3️⃣ 구조 다이어그램 (개념)

    +----------------+ | Subject | +----------------+ | + register() | | + remove() | | + notify() | +----------------+ ▲ | +----------------+ | ConcreteSubject| +----------------+ +----------------+ | Observer | +----------------+ | + update() | +----------------+ ▲ | +----------------+ |ConcreteObserver| +----------------+

    4️⃣ 코드 예시 (정석 구현)

    1) Observer 인터페이스

    public interface Observer { void update(int state); }

    2) Subject 인터페이스

    public interface Subject { void registerObserver(Observer observer); void removeObserver(Observer observer); void notifyObservers(); }

    3) ConcreteSubject

    import java.util.ArrayList; import java.util.List; public class ConcreteSubject implements Subject { private List<Observer> observers = new ArrayList<>(); private int state; @Override public void registerObserver(Observer observer) { observers.add(observer); } @Override public void removeObserver(Observer observer) { observers.remove(observer); } @Override public void notifyObservers() { for (Observer observer : observers) { observer.update(state); } } public void setState(int state) { this.state = state; notifyObservers(); } }

    4) ConcreteObserver

    public class ConcreteObserver implements Observer { private String name; public ConcreteObserver(String name) { this.name = name; } @Override public void update(int state) { System.out.println(name + " notified. New state: " + state); } }

    5) 실행 코드

    public class Main { public static void main(String[] args) { ConcreteSubject subject = new ConcreteSubject(); Observer observer1 = new ConcreteObserver("Observer1"); Observer observer2 = new ConcreteObserver("Observer2"); subject.registerObserver(observer1); subject.registerObserver(observer2); subject.setState(10); } }

    6) 출력

    Observer1 notified. Newstate:10 Observer2 notified. Newstate:10

    5️⃣ Java에서 중요한 포인트

    1) 인터페이스 기반 설계

    구현이 아닌 추상화에 의존 (DIP 원칙)

    2) 느슨한 결합 (Loose Coupling)

    Subject는 Observer의 구체 클래스 모름
    → Observer 타입만 알면 됨

    3) OCP (Open-Closed Principle)

    Observer를 추가해도 기존 Subject 코드 수정 없음

    6️⃣ Java 표준 라이브러리의 옵저버

    • Java 8 이전
      • java.util.Observer
      • java.util.Observable
      • ➡️ 존재했지만 Java 9부터 deprecated 되었습니다.
        이유
      • 상속 강제 (Observable은 클래스)
      • 유연하지 않음
      • 인터페이스 기반이 아님
    • 실무에서는 직접 구현하거나, 아래 기술을 사용
      • Spring EventListener
      • RxJava
      • Reactor (Flux, Mono)
      • 이벤트 버스 라이브러리

    7️⃣ 실무에서의 대표 사용 예

    1) Spring 이벤트 시스템

    @EventListener public void handleEvent(MyEvent event) { ... }
    → 내부적으로 옵저버 패턴 기반

    2) GUI 이벤트 리스너

    button.addActionListener(e -> { System.out.println("Clicked!"); });
    → 버튼 상태 변화 → 리스너 호출

    8️⃣ 옵저버 vs Pub-Sub 차이 (Java 관점)

    구분
    Observer
    Pub-Sub
    연결 구조
    Subject가 Observer 직접 참조
    중간에 메시지 브로커 존재
    결합도
    약하지만 직접 연결
    완전 분리
    예
    Swing 이벤트
    Kafka, Redis Pub/Sub

    9️⃣ 단점

    • Observer가 많으면 성능 문제
    • 순환 참조 위험
    • 멀티스레드 환경에서 동기화 필요

    2. 실습(ex07)

    1️⃣ polling (패턴X 옵저버가 뭔지)

    1) LotteMart.java

    C:\workspace\java_lab\designapp\src\ex07\polling\LotteMart.java
    package ex07.polling; public class LotteMart { private String value = null; // 상태 확인 public String getValue() { return value; } // 입고 public void received() { value = "상품"; } }

    2) Customer1.java

    C:\workspace\java_lab\designapp\src\ex07\polling\Customer1.java
    package ex07.polling; public class Customer1 { // 상품 있어? public void request(LotteMart lotteMart) { String value = lotteMart.getValue(); if (value != null) update(value); } // 있으면 알림 받기 public void update(String msg) { System.out.println("손님1이 받은 알림 : " + msg); } }

    3) App.java

    C:\workspace\java_lab\designapp\src\ex07\polling\App.java
    package ex07.polling; public class App { public static void main(String[] args) { LotteMart lm = new LotteMart(); Customer1 cus1 = new Customer1(); // 1. 마트에 상품 입고 확인 new Thread(() -> { for (int i = 1; i < 11; i++) { System.out.println("입고중...(" + i + "초)"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } lm.received(); System.out.println("입고완료!!"); }).start(); // 2. 손님이 풀링 } }

    4) 결과

    notion image

    5) App.java

    package ex07.polling; public class App { public static void main(String[] args) { LotteMart lm = new LotteMart(); Customer1 cus1 = new Customer1(); // 1. 마트에 상품 입고 확인 (마트 스레드) new Thread(() -> { // 10초 대기 for (int i = 1; i < 11; i++) { System.out.println("입고중...(" + i + "초)"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } // 입고 완료 lm.received(); System.out.println("입고완료!!"); }).start(); // 2. 손님이 풀링 (손님 스레드) new Thread(() -> { // 3초에 한번씩 상품 확인 요청 while (true) { System.out.println("[손님] 상품 있어?"); cus1.request(lm); try { Thread.sleep(3000); } catch (InterruptedException e) { throw new RuntimeException(e); } } }).start(); } }

    6) 결과

    notion image

    2️⃣ push (옵저버 패턴)

    1) Coustomer.java (인터페이스)

    C:\workspace\java_lab\designapp\src\ex07\push\sub\Customer.java
    package ex07.push.sub; public interface Customer { void update(String msg); }

    2) Mart.java (인터페이스)

    C:\workspace\java_lab\designapp\src\ex07\push\pub\Mart.java
    package ex07.push.pub; import ex07.push.sub.Customer; public interface Mart { // 1. 구독 등록 void add(Customer customer); // 2. 출판(입고) void received(); // 3. 알림 (구독자 update 호출) void notify(String msg); // 4. 구독 취소 void remove(Customer customer); }

    3) Cus1.java

    C:\workspace\java_lab\designapp\src\ex07\push\sub\Cus1.java
    package ex07.push.sub; public class Cus1 implements Customer{ @Override public void update(String msg) { System.out.println("손님1이 받은 알림 : " + msg); } }

    4) LotteMart.java

    C:\workspace\java_lab\designapp\src\ex07\push\pub\LotteMart.java
    package ex07.push.pub; import ex07.push.sub.Customer; import java.util.ArrayList; import java.util.List; public class LotteMart implements Mart{ private String value = null; // 상품(1개로 전제) // 임계영역(여러 스레드가 동시접근이 일어나게 될 때) -> 레이스 컨디션 private List<Customer> list = new ArrayList<>(); // 구독자 명단 // synchronized를 붙이면 하나의 스레드가 접근하면 다른 스레드가 접근하지 못하도록 막음 (레이스 컨디션 안 걸릴 수 있음) @Override synchronized public void add(Customer customer) { list.add(customer); } @Override public void received() { value = "바나나"; // 입고 완료 notify(value); } @Override public void notify(String msg) { list.forEach(customer -> { // 1. 구독에 따라 다르게 분기해줘야 함 customer.update(msg); // push }); } @Override public void remove(Customer customer) { list.remove(customer); } }

    [참고] 크리티컬 섹션 (Critical Section, 임계영역)

    [참고] 크리티컬 섹션 (Critical Section, 임계영역)

    5) Cus2.java

    C:\workspace\java_lab\designapp\src\ex07\push\sub\Cus2.java
    package ex07.push.sub; public class Cus2 implements Customer{ @Override public void update(String msg) { System.out.println("손님2이 받은 알림 : " + msg); } }

    6) App.java

    C:\workspace\java_lab\designapp\src\ex07\push\App.java
    package ex07.push; import ex07.push.pub.LotteMart; import ex07.push.pub.Mart; import ex07.push.sub.Cus1; import ex07.push.sub.Cus2; import ex07.push.sub.Customer; /** * push 방식 (옵저버패턴) * 1. 구현방식 정해져 있음 * 2. 자바 lib : Reactive Java (자바9), Stomp(그 외) * 3. 스프링 : Stomp(WebSocket 라이브러리) */ public class App { public static void main(String[] args) { // 1. 객체 생성 Mart lottemart = new LotteMart(); Customer cus1 = new Cus1(); Customer cus2 = new Cus2(); // 2. 구독 lottemart.add(cus1); lottemart.add(cus2); // 3. 구독취소 lottemart.remove(cus1); // 4. 출판 new Thread(() -> { lottemart.received(); }).start(); } }
    notion image
    package ex07.push; import ex07.push.pub.LotteMart; import ex07.push.pub.Mart; import ex07.push.sub.Cus1; import ex07.push.sub.Cus2; import ex07.push.sub.Customer; /** * push 방식 (옵저버패턴) * 1. 구현방식 정해져 있음 * 2. 자바 lib : Reactive Java (자바9), Stomp(그 외) * 3. 스프링 : Stomp(WebSocket 라이브러리) */ public class App { public static void main(String[] args) { // 1. 객체 생성 Mart lottemart = new LotteMart(); Customer cus1 = new Cus1(); Customer cus2 = new Cus2(); // 2. 구독 lottemart.add(cus1); System.out.println("손님1 구독됨"); lottemart.add(cus2); System.out.println("손님2 구독됨"); // 3. 구독취소 lottemart.remove(cus1); System.out.println("손님1 구독 취소"); // 4. 출판 System.out.println("롯데마트 입고 시작"); lottemart.received(); } }
    notion image
    Share article
    Contents
    1. 옵저버 패턴 (Observer Pattern)1️⃣ 옵저버 패턴이란?2️⃣ Java에서의 역할 구조3️⃣ 구조 다이어그램 (개념)4️⃣ 코드 예시 (정석 구현)5️⃣ Java에서 중요한 포인트6️⃣ Java 표준 라이브러리의 옵저버7️⃣ 실무에서의 대표 사용 예8️⃣ 옵저버 vs Pub-Sub 차이 (Java 관점)9️⃣ 단점2. 실습(ex07)1️⃣ polling (패턴X 옵저버가 뭔지)2️⃣ push (옵저버 패턴)

    DailyCoding

    RSS·Powered by Inblog