[디자인패턴] 8. Observer Pattern

문정준's avatar
Jul 23, 2025
[디자인패턴] 8. Observer Pattern

Observer Pattern

  • 관찰자를 설정하여 해당 상태를 객체에게 알려주는 패턴
 

Observer vs Listener

  • 특정 이벤트를 두고 Observer와 Listener의 동작이 다름
  • Observer는 해당 이벤트가 발생했는지 탐지만 수행 : 이후 로직을 개발자가 작성해야 함
    • Publisher → Observer → Subscriber의 구조 (중계)
  • Listener는 해당 이벤트가 발생하면 지정된 동작을 수행
    • Publisher(Source) → Listener의 구조 (직접 콜백)
 

Observer Pattern의 성질

  • Observer가 해당 상태를 구독
  • 구독 중인 상태가 변했을 경우, Observer는 Subscriber에게 두 가지 경우를 통해 상태를 전달
    • Polling (폴링) : 상대의 요청이 있어야 전달 가능 (req - resp 통신, 통신이 끊김)
    • Push (푸시) : Observer가 Subscriber에게 일방적인 resp 전송 (통신을 끊지 않음)
 

Ex. 마트 재고 구독

  • 손님이 상품을 구매하기 위해서 마트의 재고를 구독
 

Q. 구독 / 출판 / 알림의 책임은 누구에게 있는가?

  • 일반적으로 사용자가 해당 상태를 구독한다고 생각하지만, ‘구독’이라는 행위의 소유자는 출판사임
    • 사용자는 해당 출판사의 구독을 호출함 → 출판사가 자신을 구독하도록 요청
  • 구독, 출판, 알림은 모두 출판사가 담당함 : 사용자는 알림을 받는 역할만 수행
 
notion image
 
notion image

Polling

  • Polling에서는 사용자가 해당 출판사에게 상태가 변경되었는지 확인해야 함 (req)
    • 출판사는 사용자에게 해당 상태에 대한 응답을 전달 (resp)
    • 그 결과 req-resp의 반이중 통신이 여러 번 진행되는 방식으로 진행
      • 요청의 타이밍에 따라 서버의 성능에 영향을 주게 됨
  • App.java
public class App { public static void main(String[] args) { Customer1 c1 = new Customer1(); LotteMart lotteMart = new LotteMart(); // 1. 마트는 입고 준비 new Thread(() -> {lotteMart.received();}).start(); // 2. 입고 확인 while(true) { try { Thread.sleep(100); // 요청하는 타이밍 String value = lotteMart.getValue(); if(value != null) { c1.update(value + "이(가) 들어왔습니다."); break; } else { System.out.println("상품이 준비되지 않았습니다."); } } catch (InterruptedException e) { throw new RuntimeException(e); } } } }
  • LotteMart.java
public class LotteMart { private String value = null; public String getValue() { return value; } public void received() { for (int i = 0; i < 5; i++) { try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } value = "상품"; } }
  • Customer1.java
public class Customer1 { public void update(String msg) { System.out.println("손님 1이 받은 알림 : " + msg); } }
 

Push

  • Push는 Polling과 다르게 해당 상태를 구독 중이면 통신을 끊지 않고 유지
  • 상태가 변경되면 출판사가 직접 사용자에게 알림을 전송
    • 반응형으로 작동하기 때문에 서버의 부담이 적음
  • App.java
public class App { public static void main(String[] args) { LotteMart lotteMart = new LotteMart(); Mart emart = new EMart(); Cus1 c1 = new Cus1(); Cus2 c2 = new Cus2(); lotteMart.add(c1); lotteMart.add(c2); emart.add(c1); emart.add(c2); lotteMart.remove(c2); // 출판 -> 알림 자동 : Callback new Thread(() -> lotteMart.received()).start(); new Thread(() -> emart.received()).start(); } }
  • Mart.java
public interface Mart { // 1. 구독 void add(Customer customer); // 2. 구독 취소 void remove(Customer customer); // 3. 출판 void received(); // 4. 알림 void notify(String msg); }
  • LotteMart.java
public class LotteMart implements Mart { // 구독자 목록 private List<Customer> customers = new ArrayList<>(); // 구독 추가 @Override public void add(Customer customer) { customers.add(customer); } // 구독 제거 @Override public void remove(Customer customer) { customers.remove(customer); } // 출판 @Override public void received() { for (int i = 0; i < 5; i++) { try { Thread.sleep(1000); System.out.println("."); } catch (InterruptedException e) { throw new RuntimeException(e); } } notify("LotteMart : 바나나"); } // 알림 @Override public void notify(String msg) { for (Customer customer : customers) { customer.update(msg); } } }
  • EMart.java
public class EMart implements Mart { // 구독자 목록 private List<Customer> customers = new ArrayList<>(); // 구독 추가 @Override public void add(Customer customer) { customers.add(customer); } // 구독 제거 @Override public void remove(Customer customer) { customers.remove(customer); } // 출판 @Override public void received() { for (int i = 0; i < 5; i++) { try { Thread.sleep(1000); System.out.println("."); } catch (InterruptedException e) { throw new RuntimeException(e); } } notify("EMart : 딸기"); } // 알림 @Override public void notify(String msg) { for (Customer customer : customers) { customer.update(msg); } } }
  • Customer.java
// Subscriber public interface Customer { void update(String msg); }
  • Cus1.java
public class Cus1 implements Customer { @Override public void update(String msg) { System.out.println("손님 1이 받은 알림 : " + msg); } }
  • Cus2.java
public class Cus2 implements Customer { @Override public void update(String msg) { System.out.println("손님 2가 받은 알림 : " + msg); } }
Share article

sxias