옵저버 패턴이란?
옵저버(Observer) 패턴은 객체 간의 일대다(One-to-Many) 의존성을 정의하여, 하나의 객체의 상태가 변경되었을 때 그와 의존 관계에 있는 객체들에게 자동으로 알림이 가도록 하는 디자인 패턴입니다.
"내가 상태 바뀌면 너희들한테 알려줄게!" 라는 메시지가 핵심 아이디어입니다.
핵심 구성요소
- Subject (관찰 대상): 상태를 관리하고, 상태가 변경되면 Observer에게 알립니다.
- Observer (관찰자): Subject의 상태 변경을 감지하고 이에 대응합니다.
<출처:Refactoring Guru>
옵저버 패턴을 사용하는 이유
복잡한 의존 관계를 단순화
객체 간 상태 변경을 수동으로 관리하면 결합도가 높아지고 유지보수가 어려워집니다. 옵저버 패턴은 이를 느슨하게 연결하여 코드의 유연성과 확장성을 높여줍니다.
대표적인 사용처와 시나리오
사용 예시
- GUI 프레임워크: 이벤트 기반 인터페이스 설계
- MVC 아키텍처: Model과 View의 연결 고리
- 알림 시스템: 상태 변경 시 다양한 사용자에게 알림 전송
- 게임 개발: 상태 변화에 따른 UI, AI, 사운드 등 연동
- 데이터 스트리밍/센서 시스템: 실시간 데이터 처리 구조
사용 시나리오
- GUI 이벤트 처리: 버튼 클릭 → UI 반응
- 주식 앱: 주가 변경 → 차트 갱신
- MVC 아키텍처: Model 변경 → View 자동 업데이트
옵저버 패턴의 장단점
장점
- 저결합 구조: Subject와 Observer가 인터페이스를 통해 연결되므로, 서로의 구현에 의존하지 않음
- 확장 용이: 새로운 Observer를 동적으로 추가 가능
- 자동 동기화: Subject의 상태 변경 시 모든 Observer가 자동으로 최신 상태 유지
단점
- 예측 어려움: 어떤 Observer가 등록되어 있는지 파악하기 어려움
- 디버깅 어려움: 내부 동작이 감춰져 있어 문제 파악에 시간 소요
- 성능 저하 가능성: 많은 Observer에 대한 알림 처리 비용이 증가할 수 있음
옵저버 패턴
// Subject 인터페이스
public interface Subject {
void attach(Observer o);
void detach(Observer o);
void notifyObservers();
}
// ConcreteSubject
public class NewsAgency implements Subject {
private List<Observer> observers = new ArrayList<>();
private String news;
public void setNews(String news) {
this.news = news;
notifyObservers();
}
public void attach(Observer o) {
observers.add(o);
}
public void detach(Observer o) {
observers.remove(o);
}
public void notifyObservers() {
for (Observer o : observers) {
o.update(news);
}
}
}
// Observer 인터페이스
public interface Observer {
void update(String news);
}
// ConcreteObserver
public class NewsChannel implements Observer {
private String news;
public void update(String news) {
this.news = news;
System.out.println("Received news: " + news);
}
}
옵저버를 관리하기 위한 전략들
상태가 많을 때
조건부 알림
모든 Observer에게 무조건 알림을 보내는 대신, 관심 있는 상태에만 반응하도록 필터링합니다.
public void notifyObservers(String stateChanged) {
for (Observer o : observers) {
if (o.isInterestedIn(stateChanged)) {
o.update(stateChanged);
}
}
}
상태 캡슐화
문자열이 아닌 클래스를 사용하여 상태의 의미를 명확히 해야합니다.
class State {
String name;
Object payload;
}
옵저버가 많을 때
채널/토픽 기반 그룹화 (Pub/Sub)
모든 옵저버를 한 리스트에 넣는 게 아니라, 주제별 그룹으로 묶는 방식입니다. 주제별로 관리할 수 있다는 점과 옵저버를 새로 추가/제거하는게 간편하다는 점이 장점입니다.
Map<String, List<Observer>> topicObservers = new HashMap<>();
public void attach(String topic, Observer o) {
topicObservers.computeIfAbsent(topic, k -> new ArrayList<>()).add(o);
}
public void notifyObservers(String topic, String message) {
for (Observer o : topicObservers.getOrDefault(topic, Collections.emptyList())) {
o.update(message);
}
}
비동기 처리
옵저버가 많으면 동기적으로 하나씩 처리하면 성능 저하가 발생할 수 있습니다. 이벤트 큐 또는 쓰레드 풀을 사용해서 비동기 처리로 분산하는 방법도 있습니다.
ExecutorService executor = Executors.newFixedThreadPool(4);
public void notifyObservers(String event) {
for (Observer o : observers) {
executor.submit(() -> o.update(event));
}
}
약한 참조 사용
WeakReference<Observer> weakObserver = new WeakReference<>(observer);
약한 참조를 사용하면 옵저버 객체가 사용되지 않을때, GC가 제거하여 메모리누수를 방지할 수 있다.
중앙 관리 객체 도입: EventManager
옵저버 관리 책임을 분리하는 방식은 Subject(발행자)가 직접 Observer를 관리하지 않고, 그 역할을 별도의 EventManager(또는 NotificationCenter)에게 맡기는 구조입니다. 이렇게 하면 옵저버 등록, 제거, 알림 같은 로직이 한 곳에 집중되고, 확장성과 재사용성이 향상됩니다.
public class EventManager {
Map<String, List<Observer>> observers = new HashMap<>();
public void subscribe(String eventType, Observer o) { ... }
public void unsubscribe(String eventType, Observer o) { ... }
public void notify(String eventType, String data) { ... }
}
옵저버 패턴은 복잡한 상태 변화와 의존 관계를 효율적으로 관리할 수 있는 도구입니다. 하지만 성능, 디버깅, 메모리 관리 측면에서 고려할 사항이 많습니다. 필터링, 채널 분리, 비동기 처리 등의 전략을 잘 조합하면 훨씬 강력하고 안정적인 시스템을 만들 수 있습니다.
'개발자 공부' 카테고리의 다른 글
디자인 패턴 - 중재자(Mediator), 통신의 중앙집중화 (1) | 2025.04.20 |
---|---|
디자인 패턴 - 플라이웨이트(Flywight), 더 가볍게 (0) | 2025.04.15 |
디자인 패턴 – 프록시(Proxy), 대리자 역할 (1) | 2025.04.11 |
디자인 패턴 - 팩토리 메서드(Factory Method) (0) | 2025.04.09 |
디자인 패턴 - 브릿지 패턴(Bridge Pattern), 기능과 구현을 독립적으로 확장 (0) | 2025.04.07 |