본문 바로가기
SWE/C++ OOP

[디자인패턴] Observer Pattern 옵저버 패턴

by S나라라2 2021. 8. 9.
반응형

Observer Pattern 옵저버 패턴

 

개념

옵저퍼 패턴이란 한 객체의 상태가 바뀌면 다른 객체들한테 새 소식을 알려줄 수 있는 패턴이다.

이 때, 소식을 전달하는 객체는 Subject서브젝트라 하고, 소식을 받는 객체들을 Observer옵저버라고 한다.

 

옵저버 패턴은 신문 구독 메커니즘과 같다.

사람들은 출판사에 구독 신청을 하고, 출판사는 신문을 구독하고 있는 사람들에게만 신문을 발행한다.

출판사는 subject이고, 신문을 받아보는 구독자들은 observer이다.

 

서브젝트는 한 개이고, 구독자들은 여러 개인 일대다 관계이다.

변화가 있을 때 서브젝트는 모든 객체들에게 그 변화를 알리는 것이 아니라, 구독 신청을 한 객체들(즉 옵저버들)에게만 알린다.

출처: head first Design Pattern

위의 그림에서 보면, 우측의 Dog, Cat, Mouse는 구독 신청을 하였기 때문에 옵저버이고, 서브젝트의 알림을 받고 있다. 

그러나 Duck은 구독 신청을 하지 않았기 때문에 알림을 받지 못하고 있다.

 

의존성

옵저버는 서브젝트에 의존적이다.

데이터의 주인은 서브젝트이고, 옵저버는 단순히 서브젝트에서 데이터를 갱신해 주기를 기다리는 입장이다.

이런 방법을 사용하면. 여러 객체에서 동일한 데이터를 제어하도록 하는 것에 비해 더 깔끔한 객체지향 디자인을 이룰 수 있다. 

 

옵저버 패턴 클래스 다이어그램

 

옵저버 패턴 클래스 다이어그램

subject

서브젝트를 인터페이스화 한다.

인터페이스 서브젝트에는 registerObserver, removeObserver 메소드가 있다.

따라서 객체에서 옵저버로 등록하거나 옵저버 목록에서 탈퇴하고 싶을 때는 이 메소드를 사용한다.

서브젝트의 상태 변화가 있을 때 notifyObservers를 통해 알려준다. 

 

object

옵저버들은 서브젝트의 상태가 바뀌었을 때 update를 호출하며 반영한다.

 

concreteSubject에 있는 getState(), setState()메소드를 이해하려면 데이터 전달 방식에 대해 먼저 알아야한다.

서브젝트는 옵저버에게 변화를 알릴 때, '나 변했어'라고 변했다는 사실만 알릴 수도 있고 혹은 '나 **데이터로 변했어'라고 그 데이터도 함께 알려줄 수도 있다. 각각 풀 방식, 푸시 방식이라고 일컫는다.

 

1. 풀 방식: 옵저버가 서브젝트로부터 데이터를 가져가는 방식이다.

            서브젝트가 '나 변했어'라고 변화 사실만을 알리면, 옵저버가 '**데이터 좀 줘봐 getState()' 라고 하며 데이터를 직접 가져간다.

2. 푸시 방식 : 서브젝트가 옵저버에게 데이터를 보내는 방식이다.

            '나 **데이터로 변했어' (변화를 알리면서 데이터를 함께 전달한다.)

 

 

기상 스테이션 예시

 

예시를 가지고 자세히 이해해보자.

 

예시 상황은 습도, 온도, 압력을 측정하는 weatherData가 있다. 그리고 weatherData로부터 데이터를 받아서 보여주는 display 장비들이 여러 개 있다.

 

위 상황에서 weatherData가 서브젝트이고, CurrentCoditionsDisplay, StaticsDisplay, ForecastDisplay는 옵저버이다.

서브젝트와 옵저버 모두 인터페이스화하고, 객체들은 이 인터페이스를 따른다.

 

 

 

코드 : 서브젝트, 옵저버, 디스플레이의 인터페이스

public interface Subject 
{
	// Observer를 인자로 받으며, 각각 오저버를 등록하고 제거하는 역할을 한다.
    public void registerObserver(Observer o);
    pulibc void removeObserver(Observer o);
    
    // 상태가 변경되었을 때 모든 옵저버들에게 알리기 위해 호출된다.
    public void notifyObservers();
}

public interface Observer
{
	// 기상 정보가 변경되었을 때 옵저버한테 전달되는 값들이다.
	public void update(float temp, float humidity, float prssure);
}

public interface DisplayElement
{
	public void display();
}

Subject인터페이스에는 옵저버 등록과 관련된 메소드와 기상 정보 알림을 주는 메소드가 있다.

Observer인터페이스는 정보를 전달받는 메소드를 가지고 있다.

DisplayElement 인터페이스에는 display()라는 메소드 하나 밖에 없다. 디스플레이 항목을 화면에 표시해야 하는 경우에 이 메소드를 호출하면 된다.

 

코드: WeatherData 객체

public class WeatherData implements Subject 
{
	private ArrayList observers;
    private float temperature;
    private float humidity;
    private float pressure;
    
    public WeatherData()
    {
    	observers = new ArrayList();
    }
    
    public void registerObserver(Observer o)
    {
    	observers.add(o);
    }
    
    public void removeObserver(Observer o)
    {
    	int i = observers.indexOf(o);
        if( i >= 0 )
        {
        	observers.remove(i);
        }
    }
    
    public void notifyObservers()
    {
    	for(int i=0; i<observers.size(); i++)
        {
        	Observer observer = (Observer)observers.get(i);
            observer.update(temperature, humidity, pressure);
        }
    }
    
    public void measurementsChanged()
    {
    	notifyObservers();
    }
    
    public void setMeasurements(float temperature, float humidity, float pressure)
    {
    	this.temperature = tempereature;
        this.humidity = humidity;
        this.pressure = pressure;
        measurementsChanged();
    }
}

WeatherData는 서브젝트이므로, subject 인터페이스를 상속받는다.

등록된 옵저버들을 arrayList에 저장한다.

 

코드: CurrentConditionDisplay 객체

public class CurrentConditionsDisplay implements Observer, DisplayElement
{
	private float temperature;
    private float humidity;
    private Subject weatherData;
    
    public CurrentConditionsDisplay(Subject weatherData)
    {
    	this.weatherData = weatherData;
        weatherData.registerObserver(this);
    }
    public void update(float temperature, float humidity, float pressure)
    {
    	this.temperature = temperature;
        this.humidity = humidity;
        display();
    }
    public void display()
    {
    	System.out.println("Current conditions: " + temperature
        	+ "F degrees and " + humidity + "% humidity");
    }
}

update()메소드를 통해 옵저버로부터 데이터를 전달받는다. 푸시방식

StaticsDisplay, ForecastDisplay 객체들도 동일하게 구현될테니 생략한다.

 

이렇게 하면 옵저버 패턴을 이용하여 기상 스테이션과 기상 정보가 업데이트되는 디스플레이를 구현할 수 있다.

 

 

Java 내장 Observable 클래스, Observer 인터페이스

 

위에서는 직접 구현했지만, 사실 자바에서는 Observable, Observer를 제공한다.

사용법은 서브젝트 객체에서 Observable을 상속받고, 옵저버 객체에서 Observer를 상속받으면 된다.

 

import java.util.Observable; // 필수
import java.util.Observer;

public class WeatherData extends Observable 
{
	...
	// java의 Observable은 풀방식
    // 따라서 데이터를 얻는 get메소드가 필요하다
    public float getTemperature()
    {
    }
    ...
}
import java.util.Observable; // 필수
import java.util.Observer;

public class CurrentConditionsDisplay implements Observer, DisplayElement
{
	// Java에서는 다중 상속이 안되기 때문에 Observer를 implement한다.
    
    Observable observable;
    ...
    public CurrentConditionsDisplay(Observable observable)
    {
    	this.observable = observable;
        observable.addObserver(this);
    }
    ...
}

자바의 내장 옵저버는 풀방식만을 지원한다. 

그리고 Observable이 인터페이스가 아닌 클래스로 extends 만 가능하다는 점에서 제약 사항이 생긴다.

이러한 점들을 고려하여 필요에 따라 사용하면 될 듯 하다.

 

반응형