정보보안-이론/XX에 대하여

[디자인 패턴] (GoF 행위패턴) Observer Pattern에 대하여

리덕토 2025. 7. 13. 20:28

안녕하세요! GoF 디자인 패턴 연재, 이번 시간에는 행위 패턴(Behavioral Patterns)의 한 종류로, 객체 간의 일대다(one-to-many) 의존 관계를 정의하여, 어떤 객체(Subject)의 상태가 변하면 그와 연관된 모든 의존 객체(Observer)들이 자동으로 알림을 받고 갱신되도록 만드는 옵서버(Observer) 패턴에 대해 알아보겠습니다.


옵서버(Observer) 패턴한 객체의 상태 변화를 그 객체를 '관찰(Observing)'하는 다른 객체들에게 자동으로 알려주는 발행-구독(Publish-Subscribe) 모델을 구현하는 패턴입니다.

 

가장 쉬운 예는 '유튜브 채널 구독'입니다.

  • 유튜버(Subject, 발행자)가 새로운 영상을 올리면,
  • 그 채널을 구독한 모든 구독자(Observer, 구독자)들에게 자동으로 알림이 갑니다.

여기서 중요한 점은, 유튜버는 자신의 구독자가 누구누구인지, 몇 명인지 일일이 알 필요가 없다는 것입니다. 단지 '구독자 목록'에 대고 "새 영상 올렸어요!"라고 외치기만 하면 됩니다. 구독자들 역시 다른 구독자가 누구인지 알 필요 없이, 오직 유튜버의 소식만 받습니다. 이처럼 발행자와 구독자 간의 결합을 느슨하게(Loose Coupling) 만드는 것이 옵서버 패턴의 핵심입니다

 

옵저버 패턴은 다음과 같은 객체들로 구성됩니다.

  • Subject (주체): 옵서버들을 관리(등록, 삭제)하고, 자신의 상태가 변했을 때 옵서버들에게 알리는(notify) 인터페이스를 제공합니다.
  • ConcreteSubject (구체적인 주체): Subject 인터페이스를 구현하며, 상태를 저장하고 관리합니다. 상태가 변경되면 자신에게 등록된 모든 옵서버들에게 알립니다.
  • Observer (옵서버): Subject의 상태 변화에 대한 알림을 받기 위한 업데이트 인터페이스(예: update())를 정의합니다.
  • ConcreteObserver (구체적인 옵서버): Observer 인터페이스를 구현하며, Subject로부터 알림을 받았을 때 수행할 구체적인 동작을 정의합니다.

사용예시

새로운 뉴스가 발행될 때마다 구독자들에게 알림을 보내는 뉴스 통신사 예제를 통해 옵서버 패턴을 구현해 보겠습니다.

1. 주체 (Subject) 및 옵서버 (Observer) 인터페이스 정의

먼저 표준 인터페이스들을 정의합니다.

// Observer 인터페이스
public interface Observer {
    void update(String news);
}

// Subject 인터페이스
public interface Subject {
    void registerObserver(Observer o);
    void removeObserver(Observer o);
    void notifyObservers();
}

2. 구체적인 주체 (ConcreteSubject) 클래스 구현

옵서버들을 관리하고 새로운 뉴스를 발행하는 NewsAgency 클래스를 구현합니다.

import java.util.ArrayList;
import java.util.List;

// ConcreteSubject 클래스
public class NewsAgency implements Subject {
    private List<Observer> observers;
    private String latestNews;

    public NewsAgency() {
        this.observers = new ArrayList<>();
    }

    @Override
    public void registerObserver(Observer o) {
        observers.add(o);
    }

    @Override
    public void removeObserver(Observer o) {
        observers.remove(o);
    }

    @Override
    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update(latestNews);
        }
    }

    // 새로운 뉴스가 생기면 옵서버들에게 알림
    public void setNews(String news) {
        this.latestNews = news;
        System.out.println("\n[뉴스 속보] " + news);
        notifyObservers();
    }
}

3. 구체적인 옵서버 (ConcreteObserver) 클래스 구현

뉴스를 구독하는 NewsSubscriber 클래스를 구현합니다.

// ConcreteObserver 클래스
public class NewsSubscriber implements Observer {
    private String name;

    public NewsSubscriber(String name) {
        this.name = name;
    }

    @Override
    public void update(String news) {
        System.out.println(name + " 님이 새 소식을 받았습니다: " + news);
    }
}

4. 클라이언트 (Client) 코드

클라이언트는 주체와 옵서버들을 생성하고, 구독 관계를 설정합니다.

public class NewsSystem {
    public static void main(String[] args) {
        // 1. 주체(뉴스 통신사) 생성
        NewsAgency agency = new NewsAgency();

        // 2. 옵서버(구독자)들 생성
        Observer sub1 = new NewsSubscriber("구독자A");
        Observer sub2 = new NewsSubscriber("구독자B");

        // 3. 주체에 옵서버들 등록 (구독)
        agency.registerObserver(sub1);
        agency.registerObserver(sub2);

        // 4. 주체의 상태 변경 -> 옵서버들에게 자동 알림
        agency.setNews("디자인 패턴 스터디 시작!");
        
        // 5. 옵서버 한 명 구독 취소
        agency.removeObserver(sub2);
        
        // 6. 다시 상태 변경
        agency.setNews("옵서버 패턴 학습 중!");
        
        // 실행 결과:
        //
        // [뉴스 속보] 디자인 패턴 스터디 시작!
        // 구독자A 님이 새 소식을 받았습니다: 디자인 패턴 스터디 시작!
        // 구독자B 님이 새 소식을 받았습니다: 디자인 패턴 스터디 시작!
        //
        // [뉴스 속보] 옵서버 패턴 학습 중!
        // 구독자A 님이 새 소식을 받았습니다: 옵서버 패턴 학습 중!
    }
}


결론

옵저버 패턴은 위 예시처럼 한 객체의 상태 변화에 따라 다른 객체들의 상태도 변경되어야 하지만, 어떤 객체들이 변경되어야 하는지 미리 알 수 없는 경우에서 뿐 아니라 다음의 경우에도 사용됩니다.

  • 발행-구독 모델이 필요한 모든 경우: GUI 이벤트 처리, MVC 패턴(Model-View 관계) 등
  • 객체들 간의 결합도를 최대한 낮추고 싶을 때: SubjectObserver는 서로의 구체적인 클래스를 몰라도 인터페이스를 통해 통신하므로, 독립적으로 재사용하고 수정할 수 있습니다.

옵저버 패턴을 구현하고 운영할때는 아래와 같은 사항을 주의해야합니다.

  • 예상치 못한 업데이트 순서: Subject는 등록된 Observer들을 순회하며 알림을 보내지만, 특정 순서에 따라 업데이트가 일어나야 하는 경우에는 주의가 필요합니다.
  • 불필요한 업데이트 가능성: Subject의 사소한 변화에도 모든 Observer에게 알림이 갈 수 있습니다. 이를 해결하기 위해 push 방식(Subject가 모든 정보를 보냄) 대신 pull 방식(Observer가 필요한 정보만 가져감)을 사용하기도 합니다.

즉, 옵서버 패턴은 객체 간의 느슨한 결합을 통해 유연하고 재사용 가능한 시스템을 만드는 데 핵심적인 역할을 합니다. 이벤트 기반 시스템을 구축할 때 가장 먼저 고려해볼 만한 강력한 패턴입니다. 다음으로는 상속관계에서 미리 메서드를 정의해는, Template Method 패턴을 알아보겠습니다.