♾️ Computer Science/디자인 패턴

[Design Patterns] 옵저버 패턴 (Observer Pattern)

nerowiki 2024. 4. 23. 11:48
728x90

💡 옵저버 패턴이란

옵저버들이 관찰하고 있는 대상자의 상태 변화가 있을 때마다 대상자는 직접 목록의 관찰자들에게 통지하고,
관찰자들은 알림을 받아 조치를 취하는 행동 패턴

 

즉, 어떤 객체의 상태가 변할 때 그와 연관된 객체들에게 알림을 보내는 디자인 패턴입니다.

 

쉽게 이해하자면 유튜브나 스팸 메일로 비유할 수 있습니다.
유튜브 채널은 발행자(Subject)가 되고, 유튜브 구독자는 관찰자(Observer)가 되는 구조입니다.
만약 시청자에게 채널 정보를 전달한다면 시청자가 시간을 낭비하던지,
채널 주인이 알림을 원하지 않는 고객들에게 전달하며 자원을 낭비하게 될 것입니다.
구독자들은 해당 채널을 구독함으로써 채널에 어떠한 변화가 생기면 바로 연락을 받고 탐지하게 됩니다.
반면, 구독을 해지하거나 하지 않은 시청자에게는 알림이 가지 않습니다.

 

다른 객체와의 상태 변화를 별도의 함수 호출 없이 즉각적으로 알 수 있기 때문에
이벤트에 대한 처리를 자주해야 하는 프로그램이라면 매우 효율적인 프로그램 작성이 가능합니다.

프로그래밍적으로 옵저버 패턴은 '관찰'하기 보다 갱신을 위한 힌트 정보를 '전달'받길 기다리는 것입니다.
즉, 관찰자는 사실 대상 객체로부터 수동적으로 전달받길 기다리고 있는 것입니다.

 

💡 옵저버 패턴 기본 구조

옵저버라 불리는 하나 이상의 객체를 관찰 대상이 되는 객체에 등록 시킨 후
각각의 옵저버들은 관찰 대상인 객체가 발생시키는 이벤트를 받아 처리하는 것입니다.

더보기

옵저버 패턴은 한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체들에게 연락이 가고

자동으로 내용에 갱신되는 방법으로 일대 다의 의존성을 가지며 상호작용하는 객체 사이에서는
가능하면 느슨하게 결합하는 디자인 패턴입니다.

 

1. ISubject

관찰 대상자를 정의하는 인터페이스 입니다.

 

2. Concrete Subject

관찰 당하는 대상자 / 발행자 / 게시자 입니다.
✅ Observer들을 리스트로 모아 합성하여 가지고 있습니다.
✅ Subject 역할은 관찰자인 Observer들을 내부 리스트에 등록/삭제하는 인프라를 가지고 있습니다.
✅ Subject가 상태 변경 또는 어떤 동작을 실행할 때, Observer 들에게 이벤트 알림을 발행합니다.

 

3. IObserver

구독자들을 묶는 인터페이스 입니다.

 

4. Observer

관찰자 / 구독자 / 알림 수신자 입니다.
✅ Observer들은 Subject가 발행한 알림에 대해 현재 상태를 취득합니다.
✅ Subject의 업데이트에 대해 전후 정보를 처리합니다.

 

💡 옵저버 패턴 흐름

1️⃣ 한 개의 관찰 대상자(Subject)와 여러 개의 관찰자(Observers)로 "일 대 다" 관계입니다.
2️⃣ 관찰 대상 Subject의 상태가 바뀌면 변경 사항을 옵저버에게 통보합니다. (notifyObserver)
3️⃣ 대상자로부터 통보 받은 Observer는 값을 수정, 삭제하는 등 적절히 대응합니다. (update)
4️⃣ Observer들은 언제든 Subject 그룹에서 추가, 삭제 될 수 있습니다.
     Subject 그룹에 추가될 경우 정보를 전달 받게 되며, 삭제될 경우 정보를 받을 수 없게 됩니다. 

 

💡 옵저버 패턴 구현 예제

아래 예시는 잡지사(Publisher)와 구독자(Subscriber) 관계를 통해 옵저버 관계를 구현했습니다.

 

1. ISubject

public interface Publisher {
    public void add(Observer observer);
    public void delete(Observer observer);
    public void notifyObserver();
}

 

2. Concrete Subject

public class NewsMachine implements Publisher {
    private ArrayList<Observer> observers;
    private String title;
    private String news;
    
    public NewsMachine() {
    	observers = new ArrayList<>();
    }
    
    @Override
    public void add(Observer observer) {
    	observers.add(observer);
    }
    
    @Override
    public void delete(Observer observer) {
    	int index = observers.indexOf(observer);
    	observers.remove(index);
    }
    
    @Override
    public void notifyObserver() {
    	for (Observer Observer : observers) {
        	observer.update(title, news);
        }
    }
    
    public void setNewsInfo(String title, String news) {
    	this.title = title;
        this.news = news;
        notifyObserver();
    }
    
    public String getTitle() {
    	return title;
    }
    
    public String getNews() {
    	return news;
    }
}

 

3. IObserver

public interface Observer {
	public void update(String title, String news);
}

 

4. Observer

public AnnualSubscriber implement Observer {
    private String newsString;
    private Publisher publisher;
    
    public AnnualSubscriber(Publisher publisher) {
    	this.publisher = publisher;
        publisher.add(this);
    }
    
    @Override
    public void update(String title, String news) {
    	this.newsString = title + " \n--------\n " + news;
        display();
    }
    
    private void display() {
    	System.out.println("\n\n오늘의 뉴스\n============================\n\n" + newsString);
    }
}
public class EventSubscriber implements Observer {
    private String newsString;
    private Publisher publisher;
    
    public EventSubscriber(Publisher publisher) {
    	this.publisher = publisher;
        publisher.add(this);
    }
    
    @Override
    public void update(String title, String news) {
    	newsString = title + "\n------------------------------------\n" + news;
        display();
    }
    
    private void display() {
    	System.out.println("\n\n=== 이벤트 유저 ===");
        System.out.println("\n\n" + newsString);
    }
}

 

5. Client

public class MainClass {
	public static void main(String[] args) {
        NewsMachine newsMachine = new NewsMachine();
        AnnualSubscriber annualSubscriber = new AnnualSubscriber(newsMachine);
        EventSubscriber eventSubscriber = new EventSubscriber(newsMachine);
        
        newsMachine.setNewsInfo("오늘 한파", "전국 영하 18도 입니다.");
        newsMachine.setNewsInfo("벛꽃 축제합니다", "다같이 벚꽃보러~");
    }
}
Java 내장 옵저버 객체를 사용할 수도 있겠지만 Java 내장 객체에는 치명적인 한계가 존재합니다.
단일 상속만 지원하는 자바에서 만약 발행자 역할을 해야 하는 클래스가 다른 클래스를
상속하고 있는 상태라면 java.util.Observable 클래스의 하위 클래스로 할 수 없다는 것입니다.
따라서 위와 같은 상황을 고려한다면 개발자는 옵저버 패턴을 직접 구현할 줄 알아야 합니다.

 

💡 결론

옵저버 패턴의 핵심은 합성한 객체를 리스트로 관리하고 리스트에 있는 관찰자 객체들에게
모두 메서드 위임을 통한 전파 행위를 한다는 점 입니다.
Subject의 상태 변경을 주기적으로 조회하지 않고 자동으로 감시할 수 있습니다.
또한 런타임 시점에서 발행자와 구독 알림 관계를 맺을 수 있습니다.
상태를 변경하는 객체(Subject)와 변경을 감지하는 객체(Observer)는 느슨한 결합 관계입니다.

 

다만, 구독자는 알림 순서를 제어할 수 없고 무작위 순서로 알림을 받습니다.
(하드 코딩으로 구현 가능하지만 복잡성과 결합성 증가로 추천하지 않습니다.)
또한 옵저버 패턴을 자주 구성하면 구조, 동작을 알아보기 힘들어져 코드 복잡도가 증가합니다.
다수의 옵저버 객체를 등록한 후 해지하지 않는다면 메모리 누수 발생 가능성도 존재합니다.

 

참조 자료

더보기