1. 程式人生 > >【Java】觀察者模式總結

【Java】觀察者模式總結

一、 總體大綱

二、觀察者模式定義

出版者 + 訂閱者  = 觀察者模式
觀察者模式:定義了物件之間的一對多依賴,這樣一來,當一個物件改變狀態時,它的所有依賴者都會受到通知並自動更新。

三、設計原則

為了互動物件之間的鬆耦合設計而努力

四、例項說明

1. 實現觀察者模式

實現氣象站
1. 建立介面
public interface Subject {
	// 這兩個方法都需要一個觀察者作為變數,該觀察者是用來註冊或被刪除
	public void registerObserver(Observer o);
	public void removeObserver(Observer o);
	// 當主題狀態改變時,這個方法會被呼叫,以通知所有的觀察者
	public void notifyObservers();
}

// 所有的觀察者都必須實現update()方法,以實現觀察者介面
public interface Observer {
	public void update(float temp, float humidity, float pressure);
}

// 當佈告板需要顯示時,呼叫方法display()
public interface DisplayElement {
	public void display();
}

2. 實現主題介面
public class WeatherData implements Subject {
	// ArrayList記錄觀察者
	private ArrayList observers;
	private float temperature;
	private float humidity;
	private float pressure;
	
	public WeatherData() {
		observers = new ArrayList();
	}
	
	// 當註冊觀察者時,我們只要把它加到ArrayList的後面即可
	public void registerObserver(Observer o) {
		observers.add(o);
	}
	
	// 當觀察者想取消註冊,我們把它從ArrayList中刪除即可
	public void removeObserver(Observer o) {
		int i = observers.indexOf(o);
		if (i >= 0) {
			observers.remove(i);
		}
	}
	
	// 在這裡,我們把狀態告訴每一個觀察者。因為觀察者都實現了update(),所以我們知道如何通知它們
	public void notifyObservers() {
		for (int i = 0; i < observers.size(); i++) {
			Observer observer = (Observer)observers.get(i);
			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;
	}
}

3. 建立觀察者,即佈告板
public class CurrentConditionsDisplay implements Observer, DisplayElement {
	private float temperature;
	private float humidity;
	private Subject weatherData;
	
	// 構造器需要weatherData物件(也就是主題)作為註冊之用
	public CurrentConditionsDisplay(Subject weatherData) {
		this.weatherData = weatherData;
		weatherData.registerObserver(this);
	}
	
	// 當update()被呼叫時,我們把文件和溼度儲存起來,然後呼叫display()
	public void update(float temperature, float humidity, float pressure) {
		this.temperature = temperature;
		this.humidity = humidity;
		display();
	}
	
	public void display() {
		System.out.println("Current conditions: " + temperature 
			+ "F degrees and " + humidity + "% humidity");
	}
}

4. 建立測試程式
public class WeatherStation {

	public static void main(String[] args) {
		// 首先建立一個WeatherData物件(即主題)
		WeatherData weatherData = new WeatherData();
	
		// 建立三個佈告板,並把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);
	}
}

5. 執行程式的結果
====
Current conditions: 80.0F degrees and 65.0% humidity
Avg/Max/Min temperature = 80.0/80.0/80.0
Forecast: Improving weather on the way!
Current conditions: 82.0F degrees and 70.0% humidity
Avg/Max/Min temperature = 81.0/82.0/80.0
Forecast: Watch out for cooler, rainy weather
Current conditions: 78.0F degrees and 90.0% humidity
Avg/Max/Min temperature = 80.0/82.0/78.0
Forecast: More of the same
====


2. Java內建觀察者模式

1. 實現主題
// 記得匯入正確的Observable和Observer
import java.util.Observable;
import java.util.Observer;

// 繼承Observable,這裡不需要管理註冊與刪除,讓超類代勞即可	
public class WeatherData extends Observable {
	private float temperature;
	private float humidity;
	private float pressure;
	
	public WeatherData() { }
	
	public void measurementsChanged() {
		// notifyObservers(Object arg) 該方法是主題主動推送資訊給觀察者,arg為資料物件
		// 在呼叫notifyObservers()之前,呼叫setChanged()指示狀態以及改變
		setChanged();
		notifyObservers();
	}
	
	public void setMeasurements(float temperature, float humidity, float pressure) {
		this.temperature = temperature;
		this.humidity = humidity;
		this.pressure = pressure;
		measurementsChanged();
	}
	
	// 下面的三個方法,如果使用“拉”的做法,觀察者會利用這些方法取得WeatherData物件的狀態
	public float getTemperature() {
		return temperature;
	}
	
	public float getHumidity() {
		return humidity;
	}
	
	public float getPressure() {
		return pressure;
	}
}

2. 實現觀察者(即目前狀況佈告板)
import java.util.Observable;
import java.util.Observer;
	
public class CurrentConditionsDisplay implements Observer, DisplayElement {
	Observable observable;
	private float temperature;
	private float humidity;
	
	// 構造器需要Observable當引數,並將CurrentConditionsDisplay物件登記成為觀察者
	public CurrentConditionsDisplay(Observable observable) {
		this.observable = observable;
		observable.addObserver(this);
	}
	
	// 增加Observable和資料物件作為引數
	// 先確定主題Observable屬於WeatherData型別,然後利用getter方法獲取文件和溼度測量值,最後呼叫display()
	public void update(Observable obs, Object arg) {
		if (obs instanceof WeatherData) {
			WeatherData weatherData = (WeatherData)obs;
			this.temperature = weatherData.getTemperature();
			this.humidity = weatherData.getHumidity();
			display();
		}
	}
	
	public void display() {
		System.out.println("Current conditions: " + temperature 
			+ "F degrees and " + humidity + "% humidity");
	}
}

3. 測試程式
public class WeatherStation {

	public static void main(String[] args) {
		WeatherData weatherData = new WeatherData();
		CurrentConditionsDisplay currentConditions = 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);
	}
}

4. 執行程式結果
====
Forecast: Improving weather on the way!
Avg/Max/Min temperature = 80.0/80.0/80.0
Current conditions: 80.0F degrees and 65.0% humidity
Forecast: Watch out for cooler, rainy weather
Avg/Max/Min temperature = 81.0/82.0/80.0
Current conditions: 82.0F degrees and 70.0% humidity
Forecast: More of the same
Avg/Max/Min temperature = 80.0/82.0/78.0
Current conditions: 78.0F degrees and 90.0% humidity
====


3. 觀察者與Swing

// 建立一個JFrame,然後放上一個按鈕
public class SwingObserverExample {
	JFrame frame;
	
	public static void main(String[] args) {
		SwingObserverExample example = new SwingObserverExample();
		example.go();
	}
	
	public void go() {
		frame = new JFrame();
		JButton button = new JButton("Should I do it?");
		// 製造兩個觀察者
		button.addActionListener(new AngelListener());
		button.addActionListener(new DevilListener());
		frame.getContentPane().add(BorderLayout.CENTER, button);

		// Set frame properties 設定屬性
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.getContentPane().add(BorderLayout.CENTER, button);
		frame.setSize(300,300);
		frame.setVisible(true);
	}
	
	// 觀察者定義成內部類
	// 當主題(JButton)的狀態改變時,呼叫actionPerformed
	class AngelListener implements ActionListener {
		public void actionPerformed(ActionEvent event) {
			System.out.println("Don't do it, you might regret it!");
		}
	}

	class DevilListener implements ActionListener {
		public void actionPerformed(ActionEvent event) {
			System.out.println("Come on, do it!");
		}
	}
}