본문 바로가기
소프트웨어공학/디자인 패턴

Observer Pattern 옵저버 패턴 - 행동, 구독자(observers)와 주제(subject) | Design pattern 디자인 패턴

by javapp 자바앱 2021. 11. 9.
728x90

 

 

 

 

Observer Pattern

 

 

Observer pattern allows a number of observer objects

to synchronize its state with the subject's state.

 

 

Observers(구독자, 가입자, 관찰자) 에게 특정 주제(subject)를 Push 하여 전달하거나

Pull 해서 Observer 를 필요시 주제를 가져온다.

 

 

 

 

 

옵저버 패턴을 트위터에 비유하여 표현한 사진을 보니 쉽게 이해가 되는 것 같다.

출처 : https://dev.to/danlee0528/design-pattern-the-observer-pattern-3oha

 

 

 

2개의 객체가 loosely coupled 되어있을때 , 두 객체는 상호작용한다. but 그것들은 서로를 모른다.
옵저버 패턴은 subject와 loosely coupled 된 옵저버들의 객체 설계 제공한다.

왜 그럴까?

오직 한 주제가 특정 인터페이스를 구현한 옵저버에 대해 알고있다.

옵저버의 concrete class가 그것이 무엇을 하는지, 또는 그것이 어떤 것이든지 알 필요가 없다. 

언제든 새로운 옵저버 추가할 수 있다. 왜냐하면 오직 하나의 주제는 옵저버 인터페이스를 구현한 객체 리스트에 의존한다. 특정 옵저버를 런타임시 다른 옵저버로 대체할 수 있다. 마찬가지로 제거도 가능하다.

새로운 옵저버를 추가하기 위해 주제를 수정할 필요가 없다. 옵저버가 될 필요가 있는 새로운 concrete class를 가진다.
새로운 클래스 타입을 수용하기 위해 주제를 바꿀 필요가 없다.
새로운 옵저버로 등록하거나 우리는 새로운 클래스에 Observer 인터페이스를 구현해야 한다.
주제는 옵저버 인터페이스를 통해 구현한 객체에 전달될 것이다.

주제들과 옵저버들은 서로 독립적이기에 재사용할 수 있다.

주제 or 옵저버의 변경은 looesly coupled 이기에 서로 영향을 주지 않는다.
주제 인터페이스, 옵저버 인터페이스의 구현으로 되어있다면 각각은 변경에 자유롭다.

Loosely coupled designs 는 객체간 상호의존성이 최소화하기 때문에 유연하게 시스템을 변경할 수 있다.

- Head First Design Patterns -

 

 

Publisher-Subscribers

 

 

간단한 옵저버 패턴 다이어그램

 

 

 

 

적용할 설계 원칙

 

 

  • 상호작용하는 객체간은 loosely coupled (서로를 모르게 함):
  • Depends on abstraction / interface (not concretion).
  • Polymorphic composition

 

 

Subject ,  Observer 주제와 관찰자는 유일하지않기 때문에 변경가능성이 크다. 

특정 객체에 대해 Composition을 할 경우 Depends on Concretion, Tightly Coupled 이 발생할 수 있다.

 

Polymorphic Composition 을 통해 객체간 Loose Coupling

 

 

 

 


 

 

 

정보 전송 방법

Push Model  vs  Pull model

  Push Model

Subject : 변경 정보를 옵저버에게 전달
Pull Model

Subject :
변경 사실만 통지.
Observer :
필요시 변경 정보 가져옴.
Subject void notifyObservers()
{
     for (Observer ob : obList) ob.update( info );
}
void notifyObservers() 
{
    for(Observer ob : obList) ob.update();
    // 2개의 단계를 거친다.
}

Object getInfo() { return info; }
Observer void update(Object info){
     this.info = info;
}
void update() 
{
    // get info from the subject
    // 조건 추가 가능
   this.info = subject.getInfo();
}
단점 Observer 가 정보를 사용하지 않는 경우 비효율적
Observer 의 유형별로 서로 다른 정보를 전송해야할 경우 양자 간 커플링이 심화됨
두 단계로 이루어지므로 비효율적
또한 멀티 쓰레딩 환경에서 문제가 될 수 있다.

 

 

 


 

 

 

ConcreteSubject 와 ConcreteObserver는 여러개 추가 생성 가능하다.

 

subject 변경 가능하게 하기 위해 객체간 1 to n 독립으로 정의돼있다.

모든 주제에 대한 Observers 는 자동으로전달되고 업데이트 된다.

 

--> 객체간 데이터 일관성 유지

 

Observer Pattern

 

 

 

 


 

Example 

 

The Weather Station 예제

 

The Weather Station Diagram

 

 

 

Push  모델

주제 , 관찰자 인터페이스 정의

package observer.weather;

public interface Subject
{
    public void registerObsever(Observer observer); // 구독자 추가
    public void removeObserver(Observer observer);
    public void notifyObserver();
}

 

package observer.weather;

public interface Observer
{

    // push model
    public void update(float temp, float humidity, float pressure);

    public void remove();
}

푸시 모델 update ( data.. )

 

 

 

Display(행동)에 대한 인터페이스 정의

package observer.weather;

public interface DisplayElement {
    public void display();
}

 

 

 

Subject 구현

정보, 데이터..

package observer.weather;

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

public class WeatherData implements Subject
{
    float temp, humidity, pressure;

    // WeatherData 에 관심있어하는 구독자들 모음
    List<Observer> observerList;

    public WeatherData() {
        observerList = new ArrayList<Observer>();
    }

    // Subject 인터페이스 구현
    @Override
    public void registerObsever(Observer observer) {
        observerList.add(observer);
    }

    @Override
    public void removeObserver(Observer observer) {
        observerList.remove(observer);
    }

    // 다형성을 이용해 업데이트
    @Override
    public void notifyObserver() {
        for(Observer observer : observerList) observer.update(temp,humidity,pressure);
    }

    // 통지 방법에 융통성 부여
    // 통지 방법 추상화
    public void mesurementsChanged(){
        notifyObserver();
    }

    // 기상청이 호출해서 수치 업데이트
    public void setMeasurements(float temp, float humidity, float pressure)
    {
        this.temp = temp;
        this.humidity = humidity;
        this.pressure = pressure;

        mesurementsChanged();
    }
}

 

 

 

 

옵저버 행동 구현

 

package observer.weather;

// 현재 조건
public class CurrentConditionsDisplay implements Observer, DisplayElement
{
    // 관심 정보
    private float temperature, humidity, pressure;
    private Subject weatherData;                    // 탈퇴시 필요함.

    public CurrentConditionsDisplay(Subject weatherData)
    {
        this.weatherData = weatherData;

        // subject에 등록, 구독자 추가
        weatherData.registerObsever(this);
    }

    // Observer
    // PUSH model.
    @Override
    public void update(float temp, float humidity, float pressure)
    {
        temperature = temp;
        this.humidity = humidity;
        this.pressure = pressure;

        display();
    }

    @Override
    public void remove() {
        weatherData.removeObserver(this);
    }

    // DisplayElement
    @Override
    public void display()
    {
        System.out.println("Current conditions: " + temperature
                + "F degrees and " + humidity + "% humidity, "+
                pressure + " P");
    }
}

 

 

 

WeatherStation 

 

메인 적용

package observer.weather;

//- 날씨 데이터를 수집하고,
//- 주기적으로 이를 WeatherData 객체에 씀.
public class WeatherStation {
    public static void main(String[] args) {
        // create subject
        WeatherData weatherData = new WeatherData();

        // create Observers
        CurrentConditionsDisplay currentConDisplay = new CurrentConditionsDisplay(weatherData);

        // Simulation
        weatherData.setMeasurements(80,35,30.4f);
    }
}

실행 결과

 

 

 

날씨 데이터를 set  설정하면 

 

 

    // 기상청이 호출해서 수치 업데이트
    public void setMeasurements(float temp, float humidity, float pressure)
    {
        this.temp = temp;
        this.humidity = humidity;
        this.pressure = pressure;

        mesurementsChanged();
    }

 


    // 통지 방법에 융통성 부여
    // 통지 방법 추상화
    public void mesurementsChanged(){
        notifyObserver();
    }

 

    // 다형성을 이용해 업데이트
    @Override
    public void notifyObserver() {
        for(Observer observer : observerList) observer.update(temp,humidity,pressure);
    }

 

   update 에서 PUSH 방식으로 display() 메소드를 실행하여 출력

    // Observer
    // PUSH model.
    @Override
    public void update(float temp, float humidity, float pressure)
    {
        temperature = temp;
        this.humidity = humidity;
        this.pressure = pressure;

        display();
    }

 

notifyObserver에 다형성에 의해 날씨 정보가 모든 observer 들에게 전달된다.

 

 

 


 

 

PULL 모델

 

Observer 인터페이스에 메소드 등록

public interface Observer
{
    // push model
    public void update(float temp, float humidity, float pressure, float windSpeed);

    // pull model
    public void update(Subject subject);

    public void remove();
}

 

public class WeatherData implements Subject
{
    private float temp, humidity, pressure;
    private float windSpeed;
    
    // WeatherData 에 관심있어하는 구독자들 모음
    List<Observer> observerList;

	    public WeatherData() {
        observerList = new ArrayList<Observer>();
    }

    ...
    
    // 다형성을 이용해 업데이트
    @Override
    public void notifyObserver() {
        // update()
        for(Observer observer : observerList) observer.update(this); // pull model
    }

    ... 
    
    getter
}

 

update가 호출되면

// 현재 조건
public class CurrentConditionsDisplay implements Observer, DisplayElement
{
    ....
    
    // PULL model.
    @Override
    public void update(Subject subject)
    {
        if(subject instanceof WeatherData)
        {
            WeatherData weatherData = (WeatherData)subject;
            this.temperature = weatherData.getTemp();       // pulling
            this.windSpeed = weatherData.getWindSpeed();    // pulling
            display();
        }
    }
    ...
}

pulling 방식으로 

 

get 메소드에 의해 받아 들인다.

 

이 과정에서 update 와 getter 두 단계로 이루어지므로 비효율적이며
또한 멀티 쓰레딩 환경에서 문제가 될 수 있다.

 

 

 

 

 

 

 

 

 

책 참고 : Head First Design Patterns

댓글