Observer 패턴을 통해 이벤트 기반 아키텍처의 기초를 탐구합니다. 느슨한 결합과 일대다 의존성 관리로 반응형 시스템을 구축하는 방법을 학습합니다.
서론: 변화에 반응하는 시스템의 미학
“좋은 소프트웨어는 변화에 민감하게 반응한다. Observer 패턴은 이런 반응성을 우아하게 구현하는 가장 근본적인 방법이다.”
현대 소프트웨어는 끊임없이 변화하는 환경에서 동작합니다. 사용자의 클릭, 주식 가격의 변동, 센서 데이터의 변화, 시스템 상태의 업데이트… 이 모든 이벤트들에 즉시 반응하는 것이 현대 애플리케이션의 핵심입니다.
Observer 패턴은 이런 이벤트 기반 아키텍처의 출발점입니다. 1994년 GoF가 정의한 이 패턴은 단순하지만 강력합니다:
“한 객체의 상태가 변했을 때, 그 객체에 의존하는 다른 객체들에게 자동으로 알려주고 업데이트되도록 하는 일대다 의존성을 정의한다.”
Observer 패턴이 해결하는 근본적 문제:
| |
이런 문제를 어떻게 우아하게 해결할 수 있을까요?
Observer 패턴의 핵심 구조와 철학
패턴의 핵심 아이디어
Observer 패턴의 핵심은 **“느슨한 결합(Loose Coupling)”**을 통한 **“일대다 의존성 관리”**입니다.
| |
Observer 패턴의 핵심 장점
| |
Push vs Pull 모델: 두 가지 철학적 접근
Push Model: “내가 너에게 줄게”
Push 모델에서는 Subject가 Observer에게 필요한 모든 데이터를 적극적으로 전달합니다.
| |
Pull Model: “내가 필요할 때 가져갈게”
Pull 모델에서는 Observer가 Subject로부터 필요한 데이터를 선택적으로 가져옵니다.
| |
하이브리드 접근: 최선의 선택
실제 시스템에서는 두 모델의 장점을 결합한 하이브리드 접근을 자주 사용합니다.
| |
메모리 관리와 생명주기: Observer의 숨겨진 함정
Observer 패턴의 가장 큰 함정 중 하나는 메모리 누수입니다. Subject가 Observer에 대한 강한 참조를 유지하면서 발생하는 문제입니다.
| |
한눈에 보는 Observer 패턴
Observer 패턴 요약 카드
| 항목 | 내용 |
|---|---|
| 패턴명 | Observer Pattern |
| 분류 | 행동 패턴 (Behavioral) |
| 의도 | 객체 간 일대다 의존 관계를 정의하여 상태 변화 시 자동 통지 |
| 별칭 | Publish-Subscribe, Event-Listener, Dependents |
| 적용 시점 | 상태 변화에 따른 자동 알림이 필요할 때 |
| 핵심 참여자 | Subject, Observer, ConcreteSubject, ConcreteObserver |
| 관련 패턴 | Mediator, Singleton, Event Aggregator |
Push vs Pull 모델 비교
| 비교 항목 | Push 모델 | Pull 모델 |
|---|---|---|
| 데이터 전달 방식 | Subject가 데이터를 보냄 | Observer가 데이터를 가져감 |
| 결합도 | Subject가 Observer 데이터 요구 알아야 함 | 느슨함 (Subject 인터페이스만 알면 됨) |
| 효율성 | 불필요한 데이터도 전송 가능 | 필요한 데이터만 요청 |
| 구현 복잡도 | 단순 | 약간 복잡 |
| 권장 상황 | 모든 Observer가 동일 데이터 필요 | Observer별 다른 데이터 필요 |
Observer vs Mediator vs Event Bus 비교
| 비교 항목 | Observer | Mediator | Event Bus |
|---|---|---|---|
| 통신 방향 | 단방향 (Subject→Observer) | 양방향 | 단방향/양방향 |
| 결합도 | Subject-Observer 연결 | 중재자에 집중 | 완전 느슨 |
| 확장성 | Observer 추가 용이 | 중재자 복잡도 증가 | 가장 확장적 |
| 디버깅 | 중간 | 중재자에서 추적 | 어려움 |
| 적용 규모 | 소-중규모 | 중규모 | 대규모 분산 |
메모리 누수 방지 전략
| 전략 | 설명 | 구현 방법 |
|---|---|---|
| WeakReference | 자동 해제 가능한 약한 참조 | WeakHashMap, WeakReference |
| 명시적 해제 | 구독 해제 메서드 호출 | detach(), unsubscribe() |
| 생명주기 연동 | 객체 소멸 시 자동 해제 | @PreDestroy, onDestroy() |
| Disposable 패턴 | 자원 해제 추상화 | Disposable.dispose() |
현대적 Observer 구현 비교
| 구현 방식 | 특징 | 사용 프레임워크 |
|---|---|---|
| 전통적 Observer | 직접 구현, 동기 처리 | 순수 Java |
| EventListener | 인터페이스 기반 | Swing, AWT |
| PropertyChangeListener | 속성 변경 특화 | JavaBeans |
| RxJava Observable | 반응형 스트림 | RxJava |
| Flow API | JDK 표준 반응형 | Java 9+ |
| Spring Events | 애플리케이션 이벤트 | Spring Framework |
적용 체크리스트
| 체크 항목 | 설명 |
|---|---|
| 일대다 의존 관계인가? | 하나의 상태 변경이 여러 객체에 영향 |
| 느슨한 결합이 필요한가? | Subject와 Observer가 독립적으로 변해야 함 |
| 동적 구독/해제가 필요한가? | 런타임에 Observer 추가/제거 |
| 메모리 누수 대책 수립? | WeakReference 또는 명시적 해제 |
| 동기/비동기 결정? | 스레드 안전성과 성능 고려 |
결론: 이벤트 기반 아키텍처의 출발점
Observer 패턴을 깊이 탐구한 결과, 이 패턴은 현대 이벤트 기반 아키텍처의 DNA임을 확인했습니다.
Observer 패턴의 핵심 가치:
- 느슨한 결합: Subject와 Observer의 독립적 변화
- 확장성: 새로운 Observer 추가의 용이성
- 반응성: 상태 변화에 대한 즉시 대응
- 재사용성: 다양한 도메인에서의 활용 가능
현대적 진화:
| |
실무자를 위한 핵심 가이드라인:
| |
Observer 패턴은 **“변화에 반응하는 시스템”**을 만드는 가장 기본적이면서도 강력한 도구입니다. 현대의 React, Vue.js의 반응성, Spring의 이벤트 시스템, 분산 시스템의 메시지 큐까지 모든 곳에서 이 패턴의 DNA를 발견할 수 있습니다.
다음 글에서는 Strategy와 State 패턴을 탐구하겠습니다. 알고리즘의 캡슐화와 상태 기반 행동 변화를 통해 복잡한 비즈니스 로직을 우아하게 관리하는 방법을 살펴보겠습니다.
핵심 메시지: “Observer 패턴은 단순한 알림 메커니즘이 아니라, 현대 이벤트 기반 아키텍처의 철학적 기초다. 느슨한 결합을 통해 반응적이고 확장 가능한 시스템을 만드는 출발점이다.”
메모리 관리와 생명주기
- Strong Reference로 인한 메모리 누수
- Weak Reference 활용법
- Observer 등록 해제 전략
- 자동 정리 메커니즘
3.1 Weak Reference Observer
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22class WeakReferenceSubject { private List<WeakReference<Observer>> observers = new ArrayList<>(); public void attach(Observer observer) { observers.add(new WeakReference<>(observer)); } public void notifyObservers() { Iterator<WeakReference<Observer>> iterator = observers.iterator(); while (iterator.hasNext()) { WeakReference<Observer> ref = iterator.next(); Observer observer = ref.get(); if (observer == null) { // GC된 Observer 자동 제거 iterator.remove(); } else { observer.update(this); } } } }3.2 자동 해제 메커니즘
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23class AutoCleanupSubject implements Subject { private List<Observer> observers = new ArrayList<>(); private ScheduledExecutorService cleanupService; public AutoCleanupSubject() { cleanupService = Executors.newScheduledThreadPool(1); // 주기적으로 정리 작업 수행 cleanupService.scheduleAtFixedRate(this::cleanup, 1, 1, TimeUnit.MINUTES); } private void cleanup() { observers.removeIf(observer -> { try { // Observer가 여전히 유효한지 확인 observer.update(this); return false; } catch (Exception e) { // 예외 발생 시 제거 return true; } }); } }현대적 구현과 진화
- Java의 Observable/Observer (Deprecated)
- EventBus 패턴
- Reactive Streams
- Message Queue와 Event Sourcing
4.1 EventBus 패턴
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49class EventBus { private final Map<Class<?>, List<EventHandler<?>>> handlers = new ConcurrentHashMap<>(); private final ExecutorService executor = Executors.newCachedThreadPool(); public <T> void subscribe(Class<T> eventType, EventHandler<T> handler) { handlers.computeIfAbsent(eventType, k -> new ArrayList<>()).add(handler); } public <T> void publish(T event) { Class<?> eventType = event.getClass(); List<EventHandler<?>> eventHandlers = handlers.get(eventType); if (eventHandlers != null) { for (EventHandler<?> handler : eventHandlers) { executor.submit(() -> { try { ((EventHandler<T>) handler).handle(event); } catch (Exception e) { System.err.println("Error handling event: " + e.getMessage()); } }); } } } } interface EventHandler<T> { void handle(T event); } // 사용 예시 class OrderEvent { private final String orderId; private final double amount; public OrderEvent(String orderId, double amount) { this.orderId = orderId; this.amount = amount; } // getters... } class EmailNotificationService implements EventHandler<OrderEvent> { @Override public void handle(OrderEvent event) { System.out.println("Sending email for order: " + event.getOrderId()); } }4.2 Reactive Streams 연계
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24// RxJava 스타일의 Observable class ReactiveStock { private final PublishSubject<StockPrice> priceStream = PublishSubject.create(); public Observable<StockPrice> getPriceStream() { return priceStream.asObservable(); } public void updatePrice(String symbol, double price) { priceStream.onNext(new StockPrice(symbol, price)); } } // 사용 예시 ReactiveStock stock = new ReactiveStock(); // 다양한 Observer들 stock.getPriceStream() .filter(price -> price.getValue() > 100) .subscribe(price -> System.out.println("High value stock: " + price)); stock.getPriceStream() .buffer(5) // 5개씩 묶어서 처리 .subscribe(prices -> calculateAverage(prices));실제 활용 사례
- GUI 이벤트 처리
- MVC 아키텍처
- 실시간 데이터 스트리밍
- 마이크로서비스 간 통신
작성 가이드라인
접근 방식:
- 이벤트 기반 사고의 철학적 기초
- 현대 소프트웨어 아키텍처와의 연관성
- 성능과 메모리 관리의 실용적 고려사항
- Reactive Programming으로의 진화 과정
구성 전략:
- 기초 개념: Observer 패턴의 본질과 동기
- 구현 변형: Push/Pull 모델의 차이와 선택 기준
- 실무 고려사항: 메모리 누수 방지와 생명주기 관리
- 현대적 진화: EventBus, Reactive Streams로의 발전
필수 포함 요소:
- 실제 GUI 프레임워크에서의 활용 사례
- Spring Events, Google Guava EventBus 분석
- RxJava, Reactor 라이브러리와의 연관성
- 메모리 프로파일링과 성능 측정
깊이 있는 분석 포인트
메모리 관리 관점:
- Strong vs Weak Reference의 성능 차이
- GC 압박과 Observer 패턴의 상관관계
- 대규모 Observer 등록 시 메모리 최적화
동시성과 스레드 안전성:
- 멀티스레드 환경에서의 Observer 통지
- CopyOnWriteArrayList vs synchronized List
- 비동기 이벤트 처리와 백프레셔
분산 시스템 관점:
- Message Queue를 통한 분산 Observer
- Event Sourcing과 CQRS 패턴
- 마이크로서비스 간 이벤트 전파
실제 사례 분석
Swing EventListener
1 2 3 4 5 6 7 8 9 10 11 12JButton button = new JButton("Click me"); // Observer 패턴의 전형적인 활용 button.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { System.out.println("Button clicked!"); } }); // 람다 표현식으로 간소화 button.addActionListener(e -> System.out.println("Button pressed!"));Spring Application Events
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22@Component public class OrderService { @Autowired private ApplicationEventPublisher eventPublisher; public void processOrder(Order order) { // 주문 처리 로직 processOrderInternal(order); // 이벤트 발행 eventPublisher.publishEvent(new OrderProcessedEvent(order)); } } @EventListener @Component public class EmailNotificationService { @EventListener public void handleOrderProcessed(OrderProcessedEvent event) { sendConfirmationEmail(event.getOrder()); } }Android Observer 패턴
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19// LiveData - Android의 Observer 패턴 구현 public class UserRepository { private MutableLiveData<User> userLiveData = new MutableLiveData<>(); public LiveData<User> getUser() { return userLiveData; } public void updateUser(User user) { userLiveData.setValue(user); } } // Activity에서 관찰 userRepository.getUser().observe(this, user -> { if (user != null) { updateUI(user); } });
심화 주제
Observer 패턴의 고급 변형
- Hierarchical Observer (계층적 관찰자)
- Filtered Observer (필터링 관찰자)
- Batch Observer (일괄 처리 관찰자)
성능 최적화 기법
- Observer 우선순위 처리
- 지연 평가 (Lazy Evaluation)
- 이벤트 병합과 중복 제거
고급 메모리 관리
- Reference Queue를 이용한 정리
- WeakHashMap 활용
- 메모리 리크 탐지 도구
실습 과제
기본 Observer 구현:
- 주식 시세 모니터링 시스템
- 온도 센서 알림 시스템
- 파일 변경 감지기
고급 Observer 구현:
- EventBus 라이브러리 구현
- Reactive Stream 기반 데이터 파이프라인
- 분산 이벤트 시스템
성능 최적화 실습:
- 대량 Observer 성능 테스트
- 메모리 누수 시나리오 재현 및 해결
- 비동기 이벤트 처리 최적화
토론 주제들
설계 철학:
- “Push vs Pull, 어떤 상황에서 무엇을 선택해야 하는가?”
- “Observer 패턴의 느슨한 결합은 항상 좋은가?”
성능과 복잡성:
- “Observer 수가 많아질 때의 성능 임계점은?”
- “동기 vs 비동기 Observer의 선택 기준은?”
현대적 적용:
- “Reactive Programming이 Observer 패턴을 완전히 대체할 수 있는가?”
- “마이크로서비스에서 Observer 패턴의 의미는?”
성능 분석 데이터
Observer 수에 따른 성능:
| |
Push vs Pull 모델 비교:
| |
참고 자료
핵심 도서:
- Design Patterns: Elements of Reusable Object-Oriented Software (GoF)
- Reactive Programming with RxJava
- Building Event-Driven Microservices
프레임워크 분석:
- Spring Framework Event 메커니즘
- Google Guava EventBus 구현
- RxJava Observable 소스코드
현대적 적용:
- Apache Kafka Event Streaming
- Redis Pub/Sub 메커니즘
- WebSocket 실시간 통신
작성 시 주의사항
- 이론적 설명과 실제 구현의 균형 유지
- 메모리 누수 위험성을 충분히 강조
- 현대 Reactive Programming과의 연결점 명시
- 다음 글(Strategy & State)과의 연결고리 마련
평가 기준
독자가 이 글을 읽은 후 달성해야 할 목표:
- Observer 패턴의 본질과 다양한 구현 방식을 이해할 수 있다
- Push vs Pull 모델의 차이점과 선택 기준을 파악할 수 있다
- 메모리 누수 문제를 인지하고 해결 방법을 적용할 수 있다
- 현대 EventBus와 Reactive Programming의 연관성을 설명할 수 있다
- 실제 프로젝트에서 Observer 패턴을 적절히 활용할 수 있다
핵심 메시지: “Observer 패턴은 현대 소프트웨어의 이벤트 기반 아키텍처의 출발점이다. 단순한 통지 메커니즘에서 시작해서 복잡한 리액티브 시스템까지, 모든 이벤트 기반 설계의 DNA가 담겨있다. 하지만 메모리 누수와 성능 이슈를 항상 염두에 두어야 하며, 현대에는 EventBus나 Reactive Streams로 진화한 형태로 더 많이 활용된다.”
![Featured image of post [Design Patterns] 옵저버: 이벤트 드리븐 아키텍처의 핵심](/post/design-patterns/11-observer-event-driven-architecture/wordcloud_hu_ebe08963e54d376a.png)
![[Design Patterns] 전략과 상태: 알고리즘 캡슐화의 미학](/post/design-patterns/12-strategy-state-algorithm-encapsulation/wordcloud_hu_a8214976f1be2ceb.png)
![[Design Patterns] 옵저버 패턴 실습 - 이벤트 주도 아키텍처](/post/design-patterns/11-observer-event-driven-architecture-practice/wordcloud_hu_6de2e47c8eae4e3e.png)
![[Design Patterns] 옵저버: 이벤트 드리븐 아키텍처의 핵심](/post/design-patterns/11-observer-event-driven-architecture/wordcloud_hu_32bd4a96b627ed01.png)
![[Design Patterns] 브리지와 플라이웨이트 패턴 실습 - 분리와 효율성](/post/design-patterns/10-bridge-flyweight-separation-efficiency-practice/wordcloud_hu_2124c839e4786b1.png)
![[Design Patterns] 브릿지와 플라이웨이트: 분리와 효율성](/post/design-patterns/10-bridge-flyweight-separation-efficiency/wordcloud_hu_9e66a1ee1fc45f29.png)
![[Design Patterns] 인터프리터와 미디에이터: 파싱과 조정의 패턴](/post/design-patterns/15-interpreter-mediator-parsing-coordination/wordcloud_hu_8a3be5c28cc622db.png)
![[Design Patterns] 템플릿 메서드와 이터레이터: 제어의 깊이](/post/design-patterns/14-template-method-iterator-depth/wordcloud_hu_85fb20e44444c9eb.png)
![[Design Patterns] 동시성과 분산 시스템에서의 패턴](/post/design-patterns/19-concurrency-distributed-patterns/wordcloud_hu_f2a25a3614f625e5.png)
![[Design Patterns] 메멘토와 비지터: 상태 보존과 연산 분리](/post/design-patterns/16-memento-visitor-state-operation-separation/wordcloud_hu_1ee245d72e7d1bb8.png)