728x90

옵저버 패턴이란?

옵저버(Observer) 패턴은 객체 간의 일대다(One-to-Many) 의존성을 정의하여, 하나의 객체의 상태가 변경되었을 때 그와 의존 관계에 있는 객체들에게 자동으로 알림이 가도록 하는 디자인 패턴입니다.

"내가 상태 바뀌면 너희들한테 알려줄게!" 라는 메시지가 핵심 아이디어입니다.

핵심 구성요소

  • Subject (관찰 대상): 상태를 관리하고, 상태가 변경되면 Observer에게 알립니다.
  • Observer (관찰자): Subject의 상태 변경을 감지하고 이에 대응합니다.

    <출처:Refactoring Guru>

옵저버 패턴을 사용하는 이유

복잡한 의존 관계를 단순화

객체 간 상태 변경을 수동으로 관리하면 결합도가 높아지고 유지보수가 어려워집니다. 옵저버 패턴은 이를 느슨하게 연결하여 코드의 유연성과 확장성을 높여줍니다.

대표적인 사용처와 시나리오

사용 예시

  1. GUI 프레임워크: 이벤트 기반 인터페이스 설계
  2. MVC 아키텍처: Model과 View의 연결 고리
  3. 알림 시스템: 상태 변경 시 다양한 사용자에게 알림 전송
  4. 게임 개발: 상태 변화에 따른 UI, AI, 사운드 등 연동
  5. 데이터 스트리밍/센서 시스템: 실시간 데이터 처리 구조

사용 시나리오

  • 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) { ... }
}

옵저버 패턴은 복잡한 상태 변화와 의존 관계를 효율적으로 관리할 수 있는 도구입니다. 하지만 성능, 디버깅, 메모리 관리 측면에서 고려할 사항이 많습니다. 필터링, 채널 분리, 비동기 처리 등의 전략을 잘 조합하면 훨씬 강력하고 안정적인 시스템을 만들 수 있습니다.

728x90

+ Recent posts