觀察者模式概述:
定義了對象之間的一對多依賴,這樣一來,當一個對象改變狀態時,它的所有依賴者都會收到通知並自動更新。
就像報紙和雜誌的訂閱:
1、報社的業務就是出版報紙,這相當於觀察者模式裏的主題,主題裏的狀態發生變化就相當於有了新聞,報社的任務是出
版報紙,而主題的任務就是發出變化的通知。
2、向某家報社訂閱報紙,只要它們有新報紙出版,就會給你送來。只要你是它們的用戶,你就會一直收到報紙。這相當於
觀察者模式裏的觀察者,一旦主題發生變化併發出通知,那麼所有依賴該主題(觀察了該主題)的觀察者都會收到通知。
3、當你不想再看報紙的時候,取消訂閱,就不會再收到他們的報紙。這就相當於某個觀察者取消觀察了該主題,那麼主題
再發生變化的時候發出變化的通知的時候該觀察者就不會再收到通知。
4、只要報社還在運營,就會有人一直向他們訂閱報紙或者取消訂閱報紙。
觀察者模式的結構:
有些新類型的觀察者出現時,主題的代碼不需要修改。假如我們有個新的具體類需要當觀察者,我們不需要爲了兼容新類型而修改主題的代碼,所有需要做的就是在新類裏實現Observer接口,然後註冊成爲觀察者就哦了。主題不在乎別的,它只會發送通知給所有實現了Observer接口並且註冊了該主題的觀察者。
主題接口:
<span style="font-size:14px;">package cn.wang;
public interface Subject {
void registObserver(Observer o);//註冊到主題
void unregistObserver(Observer o);//取消註冊
void notifyObservers();//通知所有觀察者數據發生了變化
}</span>
觀察者接口:
<span style="font-size:14px;">package cn.wang;
public interface Observer {
void update(String temp,String humidity,String pressure);
}</span>
具體的主題,實現主題接口:
<span style="font-size:14px;">package cn.wang;
import java.util.ArrayList;
import java.util.List;
public class WeatherDataSubject implements Subject {
private float temp;
private float humidity;
private float pressure;
private List<Observer> observers;
/**
* 主題的構造方法,我們在構造方法裏維護一個Observer觀察者集合
*/
public WeatherDataSubject() {
observers = new ArrayList<Observer>();
}
@Override
// 註冊觀察者
public void registObserver(Observer o) {
observers.add(o);
}
@Override
// 取消註冊觀察者
public void unregistObserver(Observer o) {
if (observers.contains(o)) {
observers.remove(o);
}
}
@Override
public void notifyObservers() {
for (Observer o : observers) {
//這裏將float的數據轉換成String
o.update(String.valueOf(temp), String.valueOf(humidity), String.valueOf(pressure));
}
}
/**
* 氣象臺在數據變化的時候會調用該方法,不用管它是怎麼被調用的,可能是傳感器檢測到溫度變化了就會調用它
*/
private void dataSetChanged() {
// 數據發生了變化,就通知所有的觀察者
notifyObservers();
}
public void setData(float temp, float humididy, float pressure) {
this.temp = temp;
this.humidity = humididy;
this.pressure = pressure;
// 數據發生變化調用dataSetChanged方法
dataSetChanged();
}
}</span>
溫度數據的觀察者:
<span style="font-size:14px;">package cn.wang;
public class TemperatureObserver implements Observer {
private Subject subject;//這裏生成了主題的引用是爲了以後可以取消註冊。
/**
* 觀察者實現了Observer接口之後要註冊到主題,在這裏我們在構造函數裏進行註冊
*/
public TemperatureObserver(Subject subject) {
//這裏也可以註冊其他的主題,只要相應的主題實現了Subject接口即可
this.subject = subject;
subject.registObserver(this);
}
@Override
public void update(String temp, String humidity, String pressure) {
// 這種方式是推的形式,每個觀察者選擇自己需要的數據來進行操作
System.out.println("當前溫度是: " + temp);
}
}</span>
溼度數據的觀察者:
<span style="font-size:14px;">package cn.wang;
public class HumityObserver implements Observer {
private Subject subject;
public HumityObserver(Subject subject) {
this.subject = subject;
subject.registObserver(this);
}
@Override
public void update(String temp, String humidity, String pressure) {
// 這種方式是推的形式,每個觀察者選擇自己需要的數據來進行操作
System.out.println("當前的溼度是: " + humidity);
}
}</span>
氣壓數據的觀察者:
<span style="font-size:14px;">package cn.wang;
public class PressuerObserver implements Observer {
private Subject subject;
public PressuerObserver(Subject subject) {
this.subject = subject;
subject.registObserver(this);
}
@Override
public void update(String temp, String humidity, String pressure) {
// 這種方式是推的形式,每個觀察者選擇自己需要的數據來進行操作
System.out.println("當前的氣壓是: " + pressure);
}
}</span>
測試類:
<span style="font-size:14px;">package cn.wang;
public class Test {
public static void main(String[] args) {
// 實例化一個氣象站
WeatherDataSubject s = new WeatherDataSubject();
// 實例化幾個觀察者
Observer tempObserver = new TemperatureObserver(s);
Observer humidityObserver = new HumityObserver(s);
Observer pressuerObserver = new PressuerObserver(s);
//改變主題的數據
s.setData(1.0f, 0.5f, 4.3f);
s.setData(2.0f, 0.6f, 5.2f);
}
}</span>
輸出結果是:
<span style="font-size:14px;">當前溫度是: 1.0
當前的溼度是: 0.5
當前的氣壓是: 4.3
當前溫度是: 2.0
當前的溼度是: 0.6
當前的氣壓是: 5.2</span>
觀察者模式松耦合:
我們可以獨立地複用主題和觀察者。如果我們需要在別的地方使用這些主題或者觀察者,可以輕易的複用,因爲這兩者並不是緊耦合。
改變主題或觀察者的其中一方,並不會影響另一方。因爲兩者是松耦合的,所以只要它們之間的接口仍被遵守,我們就可以自由滴改變它們。其實我們編碼爲了後期更好的可擴展和可維護性,應該爲了交互對象之間的松耦合設計而努力。松耦合的設計之所以能讓我們建立有彈性的OO系統,能夠應對變化,是因爲對象之間的互相依賴降到了最低。
Java API裏的觀察者模式:
Observable和Observer接口用起來很方便,因爲許多功能都已經事先實現好了。你甚至可以用推或者拉的方式來獲取變化的數據。
java API裏觀察者的類圖:
java API裏的主題,Observable:
java API裏的觀察者,Observer接口:
具體用法:
主題:
<span style="font-size:14px;">package com.wang;
import java.util.Observable;
public class WeatherDataSubject extends Observable {
private float temp;
private float humidity;
private float pressure;
/**
* 氣象臺在數據變化的時候會調用該方法,不用管它是怎麼被調用的,可能是傳感器檢測到溫度變化了就會調用它
*/
private void dataSetChanged() {
// 數據發生了變化,就通知所有的觀察者
//在通知所有觀察者之前要先調用這個方法
setChanged();
notifyObservers();//如果不傳參,就是拉的形式
}
public void setData(float temp, float humididy, float pressure) {
this.temp = temp;
this.humidity = humididy;
this.pressure = pressure;
// 數據發生變化調用dataSetChanged方法
dataSetChanged();
}
//給各個屬性設置一個getter方法,方便觀察者拉數據
public float getTemp() {
return temp;
}
public float getHumidity() {
return humidity;
}
public float getPressure() {
return pressure;
}
}</span>
溫度觀察者:
<span style="font-size:14px;">package com.wang;
import java.util.Observable;
import java.util.Observer;
public class TemperatureObserver implements Observer {
private Observable o;
/**
* 註冊到Observable的具體類
* @param 被觀察者,Observable的子類
*/
public TemperatureObserver(Observable o) {
this.o = o;
o.addObserver(this);
}
@Override
public void update(Observable o, Object arg) {
if (o instanceof WeatherDataSubject) {
// 拉數據
System.out.println("當前的溫度是: " + ((WeatherDataSubject) o).getTemp());
}
}
}</span>
溼度觀察者:
<span style="font-size:14px;">package com.wang;
import java.util.Observable;
import java.util.Observer;
public class HumidityObserver implements Observer {
private Observable o;
public HumidityObserver(Observable o) {
this.o = o;
o.addObserver(this);
}
@Override
public void update(Observable o, Object arg) {
if(o instanceof WeatherDataSubject){
System.out.println("當前的溼度是: "+((WeatherDataSubject)o).getHumidity());
}
}
}</span>
氣壓觀察者:
<span style="font-size:14px;">package com.wang;
import java.util.Observable;
import java.util.Observer;
public class PressureObserver implements Observer {
private Observable o;
public PressureObserver(Observable o) {
this.o = o;
o.addObserver(this);
}
@Override
public void update(Observable o, Object arg) {
if(o instanceof WeatherDataSubject){
System.out.println("當前的氣壓是: "+((WeatherDataSubject)o).getPressure());
}
}
}</span>
測試類:
<span style="font-size:14px;">package com.wang;
import java.util.Observer;
public class Test {
public static void main(String[] args) {
// 實例化一個主題
WeatherDataSubject subject = new WeatherDataSubject();
// 實例化溫度觀察者
Observer tempObserver = new TemperatureObserver(subject);
// 實例化溼度觀察者
Observer humidityObserver = new HumidityObserver(subject);
// 實例化氣壓觀察者
Observer pressureObserver = new PressureObserver(subject);
//改變主題的數據
subject.setData(1.0f, 0.5f, 2.3f);
System.out.println("-----------------------------");
subject.setData(1.2f, 0.8f, 3.2f);
}
}
</span>
結果:
<span style="font-size:14px;">當前的氣壓是: 2.3
當前的溼度是: 0.5
當前的溫度是: 1.0
-----------------------------
當前的氣壓是: 3.2
當前的溼度是: 0.8
當前的溫度是: 1.2</span>