옵저버 패턴

핫다리·2023년 9월 6일
0

디자인패턴

목록 보기
2/3

옵저버 패턴

한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체에게 연락이 가고 자동으로 내용이 갱신되는 방식으로 일대다(one-to-many)의존성을 정의합니다.

예제를 통해 알아봅시다.

구현조건

  1. WeatherData 클래스에는 3가지 측정값(온도, 습도, 기압)의 게터 메소드가 있다.

  2. 새로운 기상 측정 데이터가 들어올 때마다 measurementsChanged() 메소드가 호출된다.

  3. 기상 데이터를 사용하는 디스플레이 요소 3가지를 구현한다.(현재조건 디스플레이,기상통계 디스플레이, 기상 예보 디스플레이)

  4. 디스플레이를 업데이트하도록 measurementsChanged() 메소드에 코드를 추가해야 한다.

  • 디스플레이가 추가되는 상황까지 고려해봅시다.(확장성)

위 예제를 구현에만 집중해서 만들어보겠습니다.

public class WeatherData{

	//인스턴스 변수 선언
    
    public void measurementsChanged(){
    	float temp = getTemperature();
        float humidity = getHumidity();
        float pressure = getPressure();
    
    	CurrentConditionsDisplay.update(temp, humidity, pressure);
        StatisticsDisplay.update(temp, humidity, pressure);
        ForecastDisplay.update(temp, humidity, pressure);
    }
    
    //기타 메소드
}

위 코드의 문제점이 뭘까요?

1.인터페이스가 아닌 구체적인 구현을 바탕으로 개발했습니다.
2.새로운 디스플레이가 들어올때마다 코드를 변경해야 합니다.
3.실행중 디스플레이를 추가하거나 제거할 수 없습니다.
4.바뀌는 부분을 캡슐화 하지 않았습니다.

이를 해결하기 위해 옵저버 패턴이 있습니다.


옵저버 패턴 정의

신문사와 구독자와 비슷합니다.
구독자신문사를 구독하면 소식이 있을떄마다 신문이 발행됩니다.
옵저버 객체주제를 구독하면 정보가 변경될때마다 옵저버에게 소식이 갑니다.
객체(주제)의 상태가 변경되면 그 객체에 의존하는 모든 객체(옵저버)에 연락이 갑니다.

옵저버 패턴 구조

구현 방식에는 여러가지가 있겠지만, 보통은 주제 인터페이스와 옵저버 인터페이스가 들어있는 클래스 디자인으로 구현합니다.

이런 방식은 느슨한 결합이라고 합니다.
결합도는 낮게, 응집도는 높게 하는 것이 좋습니다.

서로 상호작용은 하지만, 서로 잘 모르는 관계입니다.

  1. 주제는 옵저버가 특정 인터페이스를 구현하는것만 알고 있습니다.
  2. 옵저버는 언제든 새로 추가할 수 있습니다.
  3. 새로운 옵저버를 추가할때도 주제를 변경할 필요가 전혀 없습니다.
  4. 주제와 옵저버는 서로 독립적으로 재사용할 수 있습니다.
  5. 주제나 옵저버가 달라져도 서로에게 영향을 미치지 않습니다.

디자인 원칙: 상호작용하는 객체 사이에는 가능하면 느슨한 결합을 사용해야 한다.

예제 구현하기

코드로 보기

interface 구현

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

public interface Observer {
    public void update(float temp, float humidity, float pressure);
}

public interface DisplayElement {
    public void display()
}

subject interface 구현

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.add(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();
    }
    
    // 기타 WeatherData 메소드
}

디스플레이 요소 구현

public class CurrentConditionsDisplay implements Observer, DisplayElement {
    private float temperature;
    private float humidity;
    private WeatherData weatherData;
    
    public CurrentConditionsDisplay(WeatherData 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(" 현재 상태: 온도 " + temperature
        + "F, 습도 " + humidity + "%");
    }
}

기상 스테이션 테스트

public class WeatherStation {

	public static void main(String[] args) {
		WeatherData weatherData = new WeatherData(); //weatherData객체 생성
	
		CurrentConditionsDisplay currentDisplay = 
			new CurrentConditionsDisplay(weatherData);
		StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);
		ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData);
        
        //3개의 디스플레이 생성후 weatherData객체를 인자로 전달
	

		weatherData.setMeasurements(80, 65, 30.4f);
		weatherData.setMeasurements(82, 70, 29.2f);
		weatherData.setMeasurements(78, 90, 29.2f);
        //새로운 기상 측정값이 들어왔다고 가정
	}
}

Pull vs Push

pull : 주제가 데이터를 보내줌. 위 예제가 pull 방식이다.

pull 방식을 사용할 시, 추후 WeatherData에 '풍속' 같은 새로운 데이터가 추가된다면, 대부분의 update()에서 풍속 데이터를 사용하지 않더라도 모든 디스플레이에 있는 update() 메소드를 변경해야 한다.

push : 옵저버가 데이터를 가져옴

push 방식 사용 시, 옵저버가 필요한 데이터를 골라서 가져올 수 있다.

둘중 뭐가 좋은지는 논쟁이 있지만 push방식을 선호한다

pull -> push로 코드 변경

// WeatherData class
public void notifyObservers() {
        for(Observer observer : observers) {
           // observer.update(temperature, humidity, pressure); -> 삭제
           observer.update(); -> 추가
        }
    }
    
// Observer interface
public interface Observer {
    //public void update(float temp, float humidity, float pressure); -> 삭제
    public void update(); -> 추가
}
// 디스플레이 요소 구현
//public void update(float temperature, float humidity, float pressure) { -> 삭제
public void update() { -> 추가
//        this.temperature = temperature; -> 삭제
//        this.humidity = humidity; -> 삭제
        this.temperature = weatherData.getTemperature(); -> 추가
        this.humidity = weatherData.getHumidity; -> 추가
        display();
    }
    
profile
일단 만들고 본다

0개의 댓글