헤드 퍼스트 디자인 패턴 | 에릭 프리먼 | 한빛미디어- 교보ebook

14가지 GoF 필살 패턴!, 경력과 세대를 넘어 오랫동안 객체지향 개발자의 성장을 도와준 디자인 패턴 교과서의 화려한 귀환! 》 2005년부터 디자인 패턴 도서 분야 부동의 1위 》 디자인 패턴의 고

ebook-product.kyobobook.co.kr

 

옵저버 패턴(Observer Pattern)

한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체에게 연락이 가고 자동으로 갱신되는 방식으로 일대다 의존성을 정의한다.

 

신문사와 잡지 관계와 유사하다.

구독자가 신문사에 구독 신청, 신간이 나올때마다 배달 받는다.

구독자가 해지 신청을 하면 이상 받지 않는다.

신문사가 Subject(주제)

구독자가 Observer(관찰자)

 

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

public class ObserverEx01 {
	static interface Subject<T>{
		//옵저버 등록
		void addObserver(Observer<T> observer);
		//옵저서 삭제
		void removeObserver(Observer<T> observer);
		//상태 변화 시 알림
		void notityObservers();
		
		T getData();
	}
	
	//핵심 관찰 대상1
	static class News<T> implements Subject<T>{
		//이 주제를 관찰할 옵저버 목록
		List<Observer<T>> observers = new ArrayList<>();
		//옵저버들의 핵심 관찰 데이터
		T news;
		
		public T getNews() {
			return this.news;
		}
		public void setNews(T news) {
			this.news = news;
			notityObservers();
		}
		public void addObserver(Observer<T> observer) {
			observers.add(observer);
		}
		public void removeObserver(Observer<T> observer) {
			observers.add(observer);
		}
		public void notityObservers() {
			for(Observer<T> observer : observers)
				observer.update(this);
		}
		public T getData() {
			return news;
		}
		public String toString() {
			return "News 구독자 : "+observers.toString();
		}
	}
	//핵심 관찰 대상1
	static class Magazine<T> implements Subject<T>{
		//이 주제를 관찰할 옵저버 목록
		List<Observer<T>> observers = new ArrayList<>();
		//옵저버들의 핵심 관찰 데이터
		T magazine;
		
		public T getMagazine() {
			return this.magazine;
		}
		public void setMagazine(T magazine) {
			this.magazine = magazine;
			notityObservers();
		}
		public void addObserver(Observer<T> observer) {
			observers.add(observer);
		}
		public void removeObserver(Observer<T> observer) {
			observers.add(observer);
		}
		public void notityObservers() {
			for(Observer<T> observer : observers)
				observer.update(this);
		}
		public T getData() {
			return magazine;
		}
		public String toString() {
			return "Magazine 구독자 : "+observers.toString();
		}
	}
	
	static interface Observer<T>{
		//업데이터 받는 방식, Pull방식
		void update(Subject<T> subject);
	}
	static class Person<T> implements Observer<T>{
		String name;
		//갱신 받은 데이터 목록
		List<T> updateData = new ArrayList<>();
		
		public Person(String name) {
			super();
			this.name = name;
		}
		public void update(Subject<T> subject) {
			updateData.add(subject.getData());
		}
		@Override
		public String toString() {
			return name+" "+updateData.toString();
		}
	}
	//테스트
	public static void main(String[] args) {
		Observer<String> person1 = new Person<>("홍길동");
		Observer<String> person2 = new Person<>("임꺽정");
		Observer<String> person3 = new Person<>("김자바");
		
		//대한잡지
		Magazine<String> magazine = new Magazine<>();
		//민국뉴스
		News<String> news = new News<>();
		
		magazine.addObserver(person1);
		magazine.addObserver(person2);
		
		news.addObserver(person2);
		news.addObserver(person3);
		
		System.out.println(magazine);
		System.out.println(news);
		
		magazine.setMagazine("1월 호 잡지 출판");
		news.setNews("속보... 주저리주저리");
		
		magazine.setMagazine("2월 호 잡지 출판");
		news.setNews("오늘 날씨 정보");
		
		System.out.println(person1);
		System.out.println(person2);
		System.out.println(person3);
		
	}
}
Magazine 구독자 : [홍길동 [], 임꺽정 []]
News 구독자 : [임꺽정 [], 김자바 []]
홍길동 [1월 호 잡지 출판, 2월 호 잡지 출판]
임꺽정 [1월 호 잡지 출판, 속보... 주저리주저리, 2월 호 잡지 출판, 오늘 날씨 정보]
김자바 [속보... 주저리주저리, 오늘 날씨 정보]

Subject 인터페이스의 필수 메서드는 옵저버 등록/삭제, 갱신 이 3가지다.

Observer 인터페이스 구현은 나중에 Subject를 구독할 가능성이 있어도 구현하는 것이 좋다.

 

구조상 한개의 주제에 다수의 옵저버가 연결되어있다. 그래서 일대다 관계인 것

 

옵저버 패턴에서는 주제가 상태를 저장하고 제어한다. 반면에 옵저버는 상태를 사용하지만, 반드시 소유할 필요는 없다. 때문에 주제에서 상태가 바뀌었다는 사실을 알려주길 기다리는 의존적인 성질을 가지게 된다.

 

느슨한 결합의 위력

느슨한 결합은 객체들이 상호작용할 수 있지만, 서로를 잘 모르는 관계를 말한다. 

인터페이스에 맞게 개발하는 것을 의미한다.

 

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

가상 스테이션 구현하기

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

public class WheatherStationTest {
	static interface Subject{
		void registerObserver(Observer o);//등록
		void removeObserver(Observer o);  //삭제
		void notifyObservers();// 알림
	}
	
	@FunctionalInterface
	static interface Observer{
		void update();
	}
	
	static interface DisplayElement{
		void display();
	}
	
	
	static class WeatherData implements Subject{
		private List<Observer> 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) {
			observers.remove(o);
		}
		public void notifyObservers() {
			observers.forEach((ob)->ob.update());
		}
		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;
		}
	}
	
	static class CurrentConditionsDisplay implements Observer, DisplayElement{
		private float temperature;
		private float humidity;
		private WeatherData weatherData;

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

		@Override
		public void update() {
			this.temperature = weatherData.getTemperature();
			this.humidity = weatherData.getHumidity();
			display();
		}

		@Override
		public void display() {
			System.out.println("현재 상태: 온도 " +temperature + "F, 습도 "+humidity + "%");
		}
		
	}
	static 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 = weatherData.getTemperature();
			tempSum += temp;
			numReadings++;

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

			display();
		}

		public void display() {
			System.out.println("Avg/Max/Min 온도 = " + (tempSum / numReadings)
				+ "/" + maxTemp + "/" + minTemp);
		}
	}
	static class ForecastDisplay implements Observer, DisplayElement {
		private float currentPressure = 29.92f;  
		private float lastPressure;
		private WeatherData weatherData;

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

		public void update() {
	        lastPressure = currentPressure;
			currentPressure = weatherData.getPressure();

			display();
		}

		public void display() {
			System.out.print("기상 예보: ");
			if (currentPressure > lastPressure) {
				System.out.println("날씨가 좋아지고 있습니다.");
			} else if (currentPressure == lastPressure) {
				System.out.println("지금과 비슷합니다.");
			} else if (currentPressure < lastPressure) {
				System.out.println("쌀쌀하며 비가 올 것 같습니다.");
			}
		}
	}
	
	public static void main(String[] args) {
		WeatherData weatherData = new WeatherData();
		
		CurrentConditionsDisplay conditionsDisplay = 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);
	}
}

 

현재 상태: 온도 80.0F, 습도 65.0%
Avg/Max/Min 온도 = 80.0/80.0/80.0
기상 예보: 날씨가 좋아지고 있습니다.
현재 상태: 온도 82.0F, 습도 70.0%
Avg/Max/Min 온도 = 81.0/82.0/80.0
기상 예보: 쌀쌀하며 비가 올 것 같습니다.
현재 상태: 온도 78.0F, 습도 90.0%
Avg/Max/Min 온도 = 80.0/82.0/78.0
기상 예보: 지금과 비슷합니다.

 

 

 

 

핵심 정리

옵저버 패턴에는 Push 방식과 Pull 방식이 있다. 일반적으로 Pull방식이 더 좋다고 여겨진다.

옵저버 인터페이스만 구현하면 어떤 클래스도 옵저버 패턴에 참여할 수 있다.

 

+ Recent posts