02. Observer Pattern
시작 전
헤드퍼스트 디자인 패턴을 읽고 정리한 내용이다.
이번에는 저번시간에 이어 옵저버 패턴에 대해서 알아보려고 한다.
Observer Pattern ?
예시
내가 요즘 주의 깊게 보고 있는 복싱 글러브가 있고
이 복싱 글러브를 내가 구매한다면 어떤 일이 일어날까?
복싱 글러브 재고에서 수량이 하나가 줄어들고,
구매 홈페이지와 관리자 홈페이지에서 수량이 한개씩 줄어야한다.
이처럼 특정 재고에 대해서 여러가지가 요소들이 주시하고 있다고 해서
Observer Pattern 라는 이름이 붙은 것으로 보인다.
정의 및 특징
정의
주요 객체 ( Subject ) 에 대한 변경이 일어날 때,
해당 객체를 주시하는 객체들이 ( Observer ) 변경 내역을 감지하는 구조
특징
- 1 대 다 구조
아무래도 하나의 Subject 에 대해 많은 Observer 객체들이 상태를 주시하기 때문에
- 자동 알림 및 갱신
subject 상태가 변경되면, 등록한 Observer 들에게 알람이가고, 상태를 갱신한다.
- 비교적 느슨한 결합
subject 는 Observer 의 인터페이스만 참조하여 느슨한 결합을 유지할 수 있다.
이러한 특징 때문에, Subject 가 자신을 주시하는 Observer 들을 관리하며
Observer 가 주시하고 있는 특정 값 ( 예시에서는 복싱 글러브 수량 ) 이 변경되면
Subject 가 자신이 관리하는 Observer 들에게 알리는 구조를 띈다.
Observer Pattern in Java
observer Pattern 을 Java 에서 나타낸다면 어떻게 할 수 있을까?
Subject Interface
interface InventorySubject {
void registerObserver(InventoryObserver observer);
void removeObserver(InventoryObserver observer);
void notifyObservers(); // 모든 옵저버에게 subject 의 내용이 변겨됨을 알림
}
Observer 객체를 추가 또는 제거할 수 있는 로직과
Observer 객체의 값이 변경됨을 알리는 로직을 정의한 Subject Interface 를 정의한다.
Observer Interface
interface InventoryObserver {
void update(int currentStock);
}
Subject 의해 관리되는 Observer 인터페이스이다.
Subject 에서 Observer 에게 변경된 값을 알려주기 위해서 update method 를 정의하였다.
Subject
class BoxingGloveInventory implements InventorySubject {
private List observers;
private int stockQuantity; // 복싱 글러브 재고 수량
public BoxingGloveInventory(int initialStock) {
this.observers = new ArrayList<>();
this.stockQuantity = initialStock;
System.out.println("복싱 글러브 재고 시스템 초기화. 현재 재고: " + stockQuantity);
}
@Override
public void registerObserver(InventoryObserver observer) {
if (!observers.contains(observer)) {
observers.add(observer);
System.out.println("옵저버 등록: " + observer.getClass().getSimpleName());
}
}
@Override
public void removeObserver(InventoryObserver observer) {
observers.remove(observer);
System.out.println("옵저버 해제: " + observer.getClass().getSimpleName());
}
@Override
public void notifyObservers() {
System.out.println("\n--- 재고 변경 알림 ---");
for(InventoryObserver observer : observers) {
observer.update(this.stockQuantity); // 모든 옵저버에게 현재 재고 수량을 전달하며 업데이트 요청
}
System.out.println("--- 알림 완료 ---\n");
}
// 재고 수량을 변경하는 메서드 (Subject의 상태 변경)
public void purchaseGlove(int quantity) {
if (this.stockQuantity >= quantity) {
this.stockQuantity -= quantity;
System.out.println("복싱 글러브 " + quantity + "개 구매 완료. 남은 재고: " + this.stockQuantity);
notifyObservers(); // 재고가 변경되었으니 등록된 옵저버들에게 알립니다.
} else {
System.out.println("재고가 부족합니다. 현재 재고: " + this.stockQuantity);
}
}
public int getStockQuantity() {
return stockQuantity;
}
}
Subject 인터페이스를 구현한 Subject
객체이다.registerObserver
, removeObserver
를 구현하여 Subject 객체를 통해
Observer 를 등록 및 해제 할 수 있다.
purchaseGlove
메서드가 실행되면, notifyObservers
가 실행된다.
실행된 notifyObservers에서 등록된 Observer
들을 순회하며
변경된 값을 Observer 의 update
를 통해 전달한다.
Observer
class PurchasePageDisplay implements InventoryObserver {
private int displayedStock;
@Override
public void update(int currentStock) {
this.displayedStock = currentStock;
display();
}
public void display() {
System.out.println("[구매 홈페이지] 복싱 글러브 현재 수량: " + displayedStock + "개");
}
}
//
class AdminPageDisplay implements InventoryObserver {
private int displayedStock;
@Override
public void update(int currentStock) {
this.displayedStock = currentStock;
display();
}
public void display() {
System.out.println("[관리자 홈페이지] 복싱 글러브 현황: " + displayedStock + "개 남음");
}
//
class StockAlertSystem implements InventoryObserver {
private static final int ALERT_THRESHOLD = 5; // 재고 알림 임계치
@Override
public void update(int currentStock) {
if (currentStock <= ALERT_THRESHOLD) {
System.out.println("[재고 알림 시스템] !주의! 복싱 글러브 재고가 " + currentStock + "개로 임계치(" + ALERT_THRESHOLD + "개) 이하입니다. 담당자에게 알립니다.");
// 실제 시스템이라면 이메일, SMS 발송 등의 로직이 들어갈 수 있습니다.
}
}
}
실행
public class ObserverPatternDemo {
public static void main(String[] args) {
// 1. Subject 객체 생성 (복싱 글러브 재고 10개로 시작)
BoxingGloveInventory boxingGloveInventory = new BoxingGloveInventory(10);
// 2. Observer 객체들 생성
PurchasePageDisplay purchasePage = new PurchasePageDisplay();
AdminPageDisplay adminPage = new AdminPageDisplay();
StockAlertSystem alertSystem = new StockAlertSystem();
// 3. Observer들을 Subject에 등록
boxingGloveInventory.registerObserver(purchasePage);
boxingGloveInventory.registerObserver(adminPage);
boxingGloveInventory.registerObserver(alertSystem);
System.out.println("\n--- 첫 번째 구매 발생: 6개 구매 ---");
boxingGloveInventory.purchaseGlove(6);
}
}
결과
복싱 글러브 재고 시스템 초기화. 현재 재고: 10
옵저버 등록: PurchasePageDisplay
옵저버 등록: AdminPageDisplay
옵저버 등록: StockAlertSystem
--- 첫 번째 구매 발생: 6개 구매 ---
복싱 글러브 6개 구매 완료. 남은 재고: 4
--- 재고 변경 알림 ---
[구매 홈페이지] 복싱 글러브 현재 수량: 4개
[관리자 홈페이지] 복싱 글러브 현황: 4개 남음
[재고 알림 시스템] !주의! 복싱 글러브 재고가 4개로 임계치(5개) 이하입니다. 담당자에게 알립니다.
--- 알림 완료 ---
Observer Pattern vs Pub/Sub
Observer Pattern 의 정의에 대해서 읽어 보면
객체의 상태가 변경되면 해당 객체에 의존하는 다른 객체에
변경된 내용이 적용되는 것을 의미하는 것 같다.
pub/sub 구조와 Observer Pattern 의 구조가
비슷할 것이라는 생각이 들어 조금더 자세히 알아 보았다.
Observer Pattern vs Pub/Sub 비교
subject : Observer Pattern 에서 주가되는 객체
observer : Observer Pattern 에서 subject 객체의 값을 주시하고 있는 객체
publish : Pub/Sub 패턴에서 메세지를 발행하는 객체
subscirber : Pub/Sub 에서 메세지가 발생하면 구독하는 객체
**Observer Pattern**
객체( subject ) 의 변화가 있으면, 주시하는 객체들 ( observer ) 에게 자동으로 알림을 보내는 패턴
특징
- subject 가 observer 목록을 관리하고 상태가 변경되면 observer 들의 특정 method 를 호출한다.
- 대부분 동기적
- Subject 와 Observer 간의 의존성이 존재한다.
Pub/Sub
메세지 발행 주체 ( publish ) 와 메세지를 구독하는 ( subscriber ) 사이에 이벤트 버스와 같은
중간 계층이 존재하는 패턴, publish 는 이벤트 버스에 메세지를 발행하고
subscirber 는 이벤트 버스를 구독 함으로 서로의 존재를 알지 못한다.
특징
- 특정 채널에 메시지를 발행하고 subscriber 는 특정 주제를 구독 ( publish ) 해서 메세지를 받는다.
- publish 와 subscirber 는 서로를 알지 못한다.
- 대부분 비동기적
- 의존성이 거의 존재하지 않음으로 느슨한 결합 형태를 가진다.
결론
이런 특징들을 봤을 때 Observer Pattern 은 Subject ( 중심이 되는 객체 ) 상태가 변환 되면
Subject 가 직접 관리하는 Observer 들의 객체 상태를 직접 변경하는 구조이다.
반면 Pub/Sub 의 경우 Event Bus 와 같은 중간 계층을 사용하며
Pub 객체는 특정한 주제에 대한 이벤트에 메세지를 전송하며, Sub 객체는 특정한 주제의
이벤트를 구독하여 메세지를 받는 형태이다.
구조도 완전히 다르고 적합한 형태도 다른 것을 확인 할 수 있었다.
글을 마치며
헤드퍼스트 책을 읽으며 Observer Pattern 에 대해서 알아보는 시간이였다.
Pub/Sub 구조와 헷갈렸는데 스스로 비교하는 시간을 가지며
한층 더 깊이 있게 알 수 있었다.