옵저버 패턴(Observer Pattern)

bolee·2022년 9월 17일
1

옵저버 패턴이란?

옵서버 패턴(observer pattern)은 객체의 상태 변화를 관찰하는 관찰자들, 즉 옵저버들의 목록을 객체에 등록하여 상태 변화가 있을 때마다 메서드 등을 통해 객체가 직접 목록의 각 옵저버에게 통지하도록 하는 디자인 패턴이다.
주로 분산 이벤트 핸들링 시스템을 구현하는 데 사용된다. 발행/구독 모델로 알려져 있기도 하다.

간단하게 말하자면 어떤 객체의 상태가 변할 때 그와 연관된 객체들에게 알림을 보내는 디자인 패턴이며 1 대 1 또는 1 대 N 관계를 가질 수 있다.

옵저버 패턴을 활용하면 다른 객체의 상태 변화를 별도의 함수 호출 없이 즉각적으로 알 수 있기 때문에 이벤트에 대한 처리를 자주해야 하는 프로그램이라면 매우 효율적인 프로그램을 작성할 수 있다.

옵저버 패턴의 장단점

장점

  1. 실시간으로 한 객체의 변경사항을 다른 객체에 전파할 수 있다.
  2. 느슨한 결합(Loose Coupling)으로 시스템이 유연하고 객체간의 의존성을 제거할 수 있다.

느슨한 결함(Loose Coupling)
다른 클래스를 직접적으로 사용하는 클래스의 의존성을 줄이는 것을 말한다.
시스템을 더욱 유지 할 수 있도록 만들고, 전체 프레임워크를 더욱 안정적으로 만들고 시스템의 유연성을 증가하게 하려는 의도를 가진 포괄적인 개념이다.

단점

  1. 너무 많이 사용할 경우 상태 관리가 힘들 수 있다.
  2. 데이터 배분에 문제가 발생하면 큰 문제로 발전할 수 있다.

옵저버 패턴의 구현 원리

옵저버 패턴의 핵심은 옵저버 또는 리스너(listener)라 불리는 하나 이상의 객체를 관찰 대상이 되는 객체에 등록시킨 후 각각의 옵저버들은 관찰 대상인 객체가 발생시키는 이벤트를 받아 처리하는 것이다.

UML 다이어그램으로는 아래처럼 표현된다. 관찰 대상인 객체는 “이벤트를 발생시키는 주체”라는 의미에서 Subject로 표시되어 있다.

이벤트가 발생하면 각 옵저버는 콜백(callback)을 받는다.
notify 함수는 관찰 대상이 발행한 메시지 이외에, 옵서버 자신이 생성한 인자값을 전달할 수도 있다.

각각의 파생 옵저버는 notify 함수를 구현함으로써 이벤트가 발생했을 때 처리할 각자의 동작을 정의해야 한다.

주체에는 일반적으로 등록(register), 제거(unregister) 메서드가 있는데, 전자는 새로운 옵저버를 목록에 등록하고 후자는 목록에서 옵저버를 뺀다. 등록과 제거 메서드 이외에도, 임시로 작동을 멈추거나 재개하는 메서드를 이용해 이벤트가 계속해서 있을 때 발생하는 요청을 제어할 수도 있다.

느슨한 결합으로 즉, 상속을 통한 구현이 아닌 구성(Composition)을 이용해 한 객체에 객체를 포함시키는 것이 아닌 인터페이스(Interface)를 포함하는 방식으로 많이 구현한다.

옵서버 패턴이 많이 쓰인 시스템에서는 재귀 호출을 막는 메커니즘이 필요하다.
이벤트 X가 발생하면 옵저버A가 옵저버B를 갱신한다고 가정해보자. 그런데 옵저버B가 이 처리를 위해 옵저버A를 갱신한다면, 이는 다시 A로 하여금 이벤트 X를 발생하게 한다.
이같은 상황을 막기 위해 이벤트 X가 한번 처리된 후에는 A가 이벤트 X를 다시 발생시키지 않는 방법을 고안해야 한다.

옵저버 패턴 구현 예시

잡지사(Publisher)와 구독자(Subscriber) 관계를 통해 옵저버 패턴 구현 예시를 소개할 것이다.

잡지사에서 정보를 제공하면 년 정기구독자와 이벤트 고객에게 이러한 정보를 업데이트하는 옵저버 패턴을 구현한다고 했을 때 가능한 UML 다이어그램은 아래와 같다.

Publisher를 구현한 NewsMachine 클래스는 정보를 제공하는 Publisher가 되며 Observer객체들을 가지고 있다.
그리고 Observer 인터페이스를 구현한 AnnualSubscriber(매년 정기구독자), EventSubscriber(이벤트 고객) 클래스는 NewsMachine이 새로운 뉴스를 notifyObserver() 를 호출하면서 알려줄 때마다 Update가 호출된다.

이를 토대로 JAVA로 구현을 해보자

Publisher 인터페이스 정의

Observer들을 관리하는 인터페이스로 옵저버 등록(add), 제외(delete), 옵저버들에게 정보를 알려줌(notifyObserver) 메소드를 가지고 있다.

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

Observer 인터페이스 정의

정보를 업데이트하는 메소드를 가지고 있다.

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

NewsMachine 클래스

Publisher 인터페이스를 구현한 클래스로, 정보를 제공해주는 것을 담당한다.

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;
    }
}

AnnualSubscriber, EventSubscriber 클래스

Observer를 구현한 클래스들로, notifyObserver() 메소드가 호출될 때마다 update() 메소드가 호출된다.

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);
    }
}

main 함수

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("벛꽃 축제합니다", "다같이 벚꽃보러~");
    }
}

결과

main 함수를 실행한 결과이다.

오늘의 뉴스
============================

오늘의 한파
--------
전국 영하 18도 입니다.

=== 이벤트 유저 ===

오늘의 뉴스
------------------------------------
전국 영하 18도 입니다.


오늘의 뉴스
============================

벛꽃 축제합니다
--------
다같이 벚꽃보러~

=== 이벤트 유저 ===

벛꽃 축제합니다
------------------------------------
다같이 벚꽃보러~

정리

  • 옵저버 패턴은 한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체들에게 연락이 가고 자동으로 정보가 갱신되는 1:N 의 관계(또는 1:1)를 정의한다.
  • 연결은 인터페이스를 이용하여 느슨한 결합성을 유지한다. 주체, 옵저버 인터페이스를 적용한다.
  • JAVA에서 기본으로 Observable 클래스와 Observer 인터페이스를 제공한다.
    • Observable 클래스의 경우 클래스로 구현되어 있기 때문에 사용하려면 상속을 해야한다. 따라서 다른 상속을 함께 이용할 수 없는 단점 존재한다.
  • Swift, Android 등에서 UI 관련된 곳 뿐만 아니라 다양한 곳에서 사용된다.

참조 자료

0개의 댓글