북극곰의 개발일기

옵저버(Observer) Pattern(1)





posted by purplebeen on Wed Jan 10 2018 19:04:06 GMT+0900 (KST) in JAVA


한 이틀 피곤해서 뻗는 바람에 제대로 하지 못했다. 오늘은 지난번에 했던 스트래티지 패턴에 이어서 옵저버 패턴(Observer Pattern)에 대해서 알아보자.

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

옵저버 패턴을 구현하는 방법에는 여러가지가 있지만, 대부분 주제(Subject) 인터페이스와 옵저버(Observer) 인터페이스가 들어있는 클래스 디자인을 바탕으로 한다.

주제역할을 하는 구상 클래스는 항상 subject Interface를 구현 해야 하고,

이 클래스 안에서 옵저버들을 등록, 해제를 위한 메소드 이외의 상태 변화가 생겼을때는 notifyObserve() 메소드를 이용하여 observer 객체들에게 전달해주어야 한다.

옵저버 패턴의 특징

  • 주제가 옵저버에 대해 아는 것은 옵저버가 특정 인터페이스 (Observer Interface)를 구현 한다.
  • 옵저버는 언제든지 새로 추가 될 수 있다.
  • 새로운 형식의 옵저버를 추가하려고 할 대도 주제를 전혀 변경할 필요가 없다.
  • 주제와 옵저버는 서로 독립적으로 재사용될 수 있다.
  • 주제나 옵저버가 바뀌더라도 서로에게 영향을 미치지는 않는다.

객체 지향 디자인 원칙

  • 애플리케이션에서 달라지는 부분을 찾아내고, 달라지지 않는 부분으로 부터 분리 시킨다.

    -> 달라지는 부분을 찾아서 나머지 코드에 영향을 주지 않도록 캡슐화

  • 구현이 아닌 인터페이스에 맞춰서 프로그래밍한다.

    -> 실제 실행시 쓰이는 객체가 코드에 의해서 고정되지 않도록, 어떤 상위형식(super type)에 맞춰서 프로그래밍함으로써 다형성을 활용해야함.

  • 상속보다는 구성을 활용한다.

  • 서로 상호작용 하는 객체 사이에서는 가능하면 느슨하게 결합하는 디자인을 사용해야 한다.

    -> 객체 사이의 상호 의존성을 최소화하여 변경 사항이 생겨도 무난히 처리할 수 있는 유연한 객체지향 시스템을 구축하기 위함.

자 이제 그러면 실질적으로 옵저버 패턴을 적용해서 프로그램을 짜 보자.

기상 스테이션이 존재하고 WeatherData라는 객체를 이용해서 사용자에게 받아온 정보를 바탕으로 3가지의 화면을 구성하는 프로그램을 짜야 한다.

먼저 Subject Interface와 Observer Interface, 그리고 화면이 표시될때 사용하기 위한 DisplayElements라는 Interface를 생성하자.

package observerPattern;

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

먼저 Subject Interface이다. Observer 객체를 추가, 제거 하기 위한 메소드들과

이를 구현한 객체에서 변경 사항이 생겼을때 Observer들에게 알리기 위한 notifyObservers() 메소드가 정의되어 있다.

package observerPattern;

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

다음은 Observer 인터페이스이다. Observer가 되는 객체들이 이 인터페이스를 상속 받으며,

값이 변화할때마다 각 객체에 값을 갱신하기 이한 update(float temp, float humdit, float pressure) 라는 메소드를 정의하고 있다.

package observerPattern;

public interface DisplayElement {
	public void display();
}

어떤 장치에 보여주는 역할을 하는 display() 메소드가 정의 되어 있다.

자 이제 그러면 Subject Class를 만들어 보자.

package observerPattern;

import java.util.ArrayList;

public class WeatherData implements Subject {
	private ArrayList observers;
	private float temperature;
	private float humidity;
	private float pressure;
	
	public WeatherData() {
		observers = new ArrayList();
	}

	@Override
	public void regesterObserver(Observer o) {
		// TODO Auto-generated method stub
		observers.add(o);
	}

	@Override
	public void removeObserver(Observer o) {
		// TODO Auto-generated method stub
		int i = observers.indexOf(o);
		if(i >= 0) {
			observers.remove(i);
		}
	}

	@Override
	public void notifyObservers() {
		// TODO Auto-generated method stub
		for(int i = 0; i < observers.size(); i++) {
			Observer observer = (Observer)observers.get(i);
			observer.update(temperature, humidity, pressure);
		}
	}
	
	public void measurementChanged() {
		notifyObservers();
	}
	
	public void setMeasurements (float temperature, float humidity, float pressure) {
		this.temperature = temperature;
		this.humidity = humidity;
		this.pressure = pressure;
		measurementChanged();
	}

}

Observer로 등록한 객체들을 리스트에 넣기 위해 ArrayList를 생성 하였다.

registerObserver 메소드에서는 Arraylist observers에 Observer르 추가해준다.

removeObserver 메소드는 indexOf()를 이용하여 인자값으로 받아오는 Observer 객체의 인덱스를 구하고 그 객체를 ArrayList에서 제거시킨다.

변경된 값을 설정하기 위한 setter 메소드인 setMeasurements 메소드를 생성하여 새롭게 변화한 값들을 설정해주고 값이 변경되었으므로

measurementChanged() 메소드가 실행된다.

또한 measurementChanged() 메소드는 notifyObservers 메소드를 실행하게 되고,

notifyObservers 메소드가 ArrayList에 등록된 Observer에 대하여 값을 update 시킨다.

Subject, Observer 인터페이스가 생성되었고 Subject Class인 WeatherData도 생성되었다.

그럼 이제 사용자에게 직접 화면을 보여주는 Observer Class를 만들어 보자.

package observerPattern;

public class CurrentConditionDisplay implements Observer, DisplayElement {
	private float temperature;
	private float humidity;
	private Subject weatherData;
	
	public CurrentConditionDisplay(Subject weatherData) {
		this.weatherData = weatherData;
		weatherData.regesterObserver(this);
	}
	@Override
	public void display() {
		// TODO Auto-generated method stub
		System.out.println("current conditions : " + temperature + "F degrees and " 
+ humidity + "% humidity");
	}

	@Override
	public void update(float temperature, float humidity, float pressure) {
		// TODO Auto-generated method stub
		this.temperature = temperature;
		this.humidity = humidity;
		display();
		
	}

}

생성자에서 weatherData라는 Subject 객체가 전달되며, 그 객체를 써서 디스플레이를 옵저버에 등록한다.

업데이트에서는 Subject 객체에서 변화가 있을때 받아오는 값들을 다시 설정하게 만들었고, display() 메소드를 실행시킨다.

마지막으로 display()에서는 우리가 원하는 형태로 출력시킨다.

자 이제 여태까지 우리가 옵저버 패턴을 이용해 만든 소스를 한번 테스트 해보자.

package observerPattern;

public class WeatherStation {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		WeatherData weatherData =  new WeatherData();
		
		CurrentConditionDisplay CurrentDisplay = new CurrentConditionDisplay(weatherData);
		
		weatherData.setMeasurements(80, 65, 30.4f);
		weatherData.setMeasurements(82, 70, 29.2f);
		weatherData.setMeasurements(78, 90, 29.2f);
	}

}

WeatherData 객체를 생성하여 CurrentConditionDisplay 객체를 생성할 때 인자값으로 넘겨주었다.

또한 Subject 객체가 지속적으로 변화하는 모습을 보여주기 위하여 WeatherData 객체의 setMeasurements메소드를 사용하여 값을 변경 시켜 주었다.

이를 실행시켜주면

Subject 객체의 값만 수정해줬을 뿐인데 Observed 객체인 CurretntConditionDisplay의 값도 변화하여 출력되는것을 알 수 있다.