Design Pattern : Observer

0ne·2024년 6월 15일

DesignPattern

목록 보기
2/5

정의

옵저버 패턴(Observer Pattern)에서는 한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체들한테 연락이 가고, 자동으로 내용이 갱신되는 방식으로 일대다(one-to-many) 의존성을 정의한다.

예시로 기억하자

e.g.

WeatherData 객체는 온도, 습도, 기압 정보를 가지고 있으며, 해당 정보가 변경될 때마다 다음의 세 객체에 데이터가 갱신되어야 한다. 1)현재 날씨 2) 날씨 예보 3) 기상 통계

쓰레기 코드

class WeatherData {
	getTemperature()
	getHumidity()
	getPressure()
	measurementsChanged() // 기상 관측값이 갱신되면 해당 메소드가 호출됨
    
	public void measurementsChanged() {
		float temp = getTemperature();
		float humidity = getHumidity();
		float pressure = getPressure();
    
		currentWeatherDisplay.update(temp, humidity, pressure);
		forecastWeatherDisplay.update(temp, humidity, pressure);
		staticsticDisplay.update(temp, humidity, pressure);
	}
}

문제점 : 다른 화면을 추가하고자 한다면 measurementsChanged() 메소드를 고쳐야 함
해결책 : 바뀔 수 있는 부분을 encapsulate 해야 함

observer pattern을 사용한 개선

1. subject + subject impl

public interface Subject {
	public void registerObserver(Observer o);
	public void removeObserver(Observer o);
	public void notifyObservers();
}

class WeatherData implements Subject {
	registerObserver()
	removeObserver()
	notifyObserver()
    
	getTemperature()
	getHumidity()
	getPressure()
	measurementsChanged()
}

구체적인 code

public class WeatherData implements Subject {
	private List<Observer> observers;
	private float temperature;
	private float humidity;
	private float pressure;
	
	public WeatherData() {
		observers = new ArrayList<Observer>();
	}
	
	public void registerObserver(Observer o) {
		observers.add(o);
	}
	
	public void removeObserver(Observer o) {
		observers.remove(o);
	}
	
	public void notifyObservers() {
		for (Observer observer : observers) {
			observer.update(temperature, humidity, pressure);
		}
	}
	
	public void measurementsChanged() {
		notifyObservers();
	}
	
	public void setMeasurements(float temperature, float humidity, float pressure) {
		this.temperature = temperature;
		this.humidity = humidity;
		this.pressure = pressure;
		measurementsChanged();
	}

	public float getTemperature() {
		return temperature;
	}
	
	public float getHumidity() {
		return humidity;
	}
	
	public float getPressure() {
		return pressure;
	}

}

2. observer interface 정의

새로 갱신된 subject 데이터를 전달하는 interface를 정의해야 함

interface Observer {
	update() // 새로 갱신된 주제 데이터를 전달하는 인터페이스
}

3. observer의 공통된 기능을 interface로 정의

이 예시의 경우, 화면에 표시하는 기능이 공통되므로 이를 interface로 정의한다.

interface DisplayElement {
	display() // 화면에 표현시키는 인터페이스
}

4. 2,3의 interface를 구현한 객체 만들기

class CurrentWeather implements Observer, DisplayElement{
	update()
	display() { // 현재 측정 값을 화면에 표시 }
}

class ForcastWeather implements Observer, DisplayElement{
	update()
	display() { // 날씨 예보 표시 }
}

class StatisticsDisplay implements Observer, DisplayElement{
	update()
	display() { // 평균 기온, 평균 습도 등 표시 }
}

하나만 구체적인 코드를 보자면,

public class StatisticsDisplay implements Observer, DisplayElement {
	private float maxTemp = 0.0f;
	private float minTemp = 200;
	private float tempSum= 0.0f;
	private int numReadings;
	private WeatherData weatherData;

	public StatisticsDisplay(WeatherData weatherData) {
		this.weatherData = weatherData;
		weatherData.registerObserver(this);
	}

	public void update(float temp, float humidity, float pressure) {
		tempSum += temp;
		numReadings++;

		if (temp > maxTemp) {
			maxTemp = temp;
		}
 
		if (temp < minTemp) {
			minTemp = temp;
		}

		display();
	}

	public void display() {
		System.out.println("Avg/Max/Min temperature = " + (tempSum / numReadings)
			+ "/" + maxTemp + "/" + minTemp);
	}
}

5. if, 새로운 객체 DiscomfortDisplay를 추가한다면?

class DiscomfortDisplay implements Observer, DisplayElement{
	update()
	display() { // 불쾌지수를 화면에 표시 }
}

main

public class WeatherStation {

	public static void main(String[] args) {
		WeatherData weatherData = new WeatherData();
	
		CurrentConditionsDisplay currentDisplay = 
			new CurrentConditionsDisplay(weatherData);
		StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);
		ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData);

		weatherData.setMeasurements(80, 65, 30.4f);
		weatherData.setMeasurements(82, 70, 29.2f);
		weatherData.setMeasurements(78, 90, 29.2f);
		
		weatherData.removeObserver(forecastDisplay);
		weatherData.setMeasurements(62, 90, 28.1f);
	}
}

특징 : loose coupling

  • The only thing the subject knows about an observer is that it implements a certain interface.
    객체간 결합도가 낮은 이유임.
  • We can add or remove new observers at any time.
    옵저버를 언제든 새로 추가, 제거할 수 있다.
  • We never need to modify the subject to add new types of
    observers.
    새로운 형식의 옵저버라 할 지라도 주제를 전혀 변경할 필요가 없다.
  • We can reuse subjects or observers independently of each other.
    주제와 옵저버는 서로 독립적으로 재사용 할 수 있다.
  • Changes to either the subject or an observer will not affect the other.
    주제나 옵저버가 바뀌더라도 서로에게 영향을 미치지 않는다.

핵심 구현 포인트

Observer_impl

  1. Subject_impl을 필드로 가지고 있어야 함
  2. Observer_impl 생성자 : 해당 필드 초기화 + observer배열에 등록
	private Subject_impl s;

	public Observer_impl(Subject_impl ss) {
    	this.s = ss;
        s.registerObserver(this)
    }
  1. update구현 : 정보 전달 받는 메서드
    *update는 Observer interface에 정의. 이를 구현해야 함
  • 이때 Subject의 정보를 받아올 수 있도록 한다.

  • Subject_impl에 정의된 execute() (* notifyObserver와 정보 전달 기능을 하는 메서드)에 쓰인다.

방법1. Subject가 변경된 데이터를 update 메서드의 인자로 직접 전달 (위 예시)

public interface Observer {
    void update(int newNumber);
}

public class DigitObserver implements Observer {
    @Override
    public void update(int newNumber) {
        System.out.println("DigitObserver: " + newNumber);

        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

방법2. Observer_impl이 Subject_impl에 대한 참조를 갖도록 하기

public interface Observer {
    void update(NumberGenerator generator);
}

public class DigitObserver implements Observer {
    @Override
    public void update(NumberGenerator generator) {
        System.out.println("DigitObserver: " + generator.getNumber());

        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Subject_impl

  1. 전달할 정보 정의
    위 예시의 경우 temperature, humidity, pressure

  2. Observer인터페이스에 정의된 update()메서드를 모든 observer에 대해 실행시키는 notifyObservers()

public void notifyObservers() {
		for (Observer observer : observers) {
			observer.update();
		}
}
  1. execute기능 메서드 : 정보 업데이트(중요) + notifyObservers를 실행시키는 메소드를 만든다.
  • 정보를 새로 업데이트 하는 기능. 1번에 정의된 곳에 할당.
  • notifyObserver를 꼭 실행시켜 통해 update를 실행시키고, 이는 1번에 정의된 필드를 활용한다.
profile
@Hanyang univ(seoul). CSE

0개의 댓글