案例引入
有一個(gè)天氣預(yù)報(bào)項(xiàng)目,需求如下:
- 氣象站可以將每天測量到的溫度、濕度、氣壓等等以公告的形式發(fā)布出去(比如發(fā)布到自己的網(wǎng)站或第三方)
- 需要設(shè)計(jì)開放型API,便于其他第三方也能接入氣象站獲取數(shù)據(jù)
- 提供溫度、氣壓、濕度的接口
- 測量數(shù)據(jù)更新時(shí),要能實(shí)時(shí)的通知給第三方
原始方案實(shí)現(xiàn)
設(shè)計(jì)一個(gè)WeatherData類,類里面的方法如下:
- getTemperature0:獲取溫度
- getHumidity0:獲取濕度
- getPressure0:獲取氣壓
- dataChange0:可以定時(shí)(如每十分鐘調(diào)用一次)調(diào)用該方法去氣象站更新溫度、濕度、氣壓數(shù)據(jù),并主動(dòng)推送給應(yīng)用者
實(shí)現(xiàn)
【當(dāng)前天氣狀況】
package com.atguigu.observer;
/**
* 顯示當(dāng)前天氣情況(可以理解成是氣象站的網(wǎng)站)
* @author Administrator
*
*/
public class CurrentConditions {
/**
* 溫度
*/
private float temperature;
/**
* 氣壓
*/
private float pressure;
/**
* 濕度
*/
private float humidity;
/**
* 更新 天氣情況,是由WeatherData來調(diào)用將最新的數(shù)據(jù)推送過來
* @param temperature
* @param pressure
* @param humidity
*/
public void update(float temperature, float pressure, float humidity) {
this.temperature = temperature;
this.pressure = pressure;
this.humidity = humidity;
display();
}
/**
* 顯示天氣情況
*/
public void display() {
System.out.println("***Today mTemperature: " + temperature + "***");
System.out.println("***Today mPressure: " + pressure + "***");
System.out.println("***Today mHumidity: " + humidity + "***");
}
}
【氣象臺(tái)的天氣數(shù)據(jù)】
package com.atguigu.observer;
/**
* 類是核心
* 1. 包含最新的天氣情況信息
* 2. 含有 CurrentConditions 對象
* 3. 當(dāng)數(shù)據(jù)有更新時(shí),就主動(dòng)的調(diào)用CurrentConditions對象的update方法(含 display), 這樣他們(接入方)就看到最新的信息
* @author Administrator
*
*/
public class WeatherData {
private float temperatrue;
private float pressure;
private float humidity;
private CurrentConditions currentConditions;
/**
* 加入新的第三方
* @param currentConditions
*/
public WeatherData(CurrentConditions currentConditions) {
this.currentConditions = currentConditions;
}
public float getTemperature() {
return temperatrue;
}
public float getPressure() {
return pressure;
}
public float getHumidity() {
return humidity;
}
public void dataChange() {
//調(diào)用 接入方的 update
currentConditions.update(getTemperature(), getPressure(), getHumidity());
}
/**
* 當(dāng)數(shù)據(jù)有更新時(shí),就調(diào)用 setData
* @param temperature
* @param pressure
* @param humidity
*/
public void setData(float temperature, float pressure, float humidity) {
this.temperatrue = temperature;
this.pressure = pressure;
this.humidity = humidity;
//調(diào)用dataChange, 將最新的信息 推送給 接入方 currentConditions
dataChange();
}
}
【主類】
package com.atguigu.observer;
public class Client {
public static void main(String[] args) {
//創(chuàng)建接入方 currentConditions
CurrentConditions currentConditions = new CurrentConditions();
//創(chuàng)建 WeatherData 并將 接入方 currentConditions 傳遞到 WeatherData中
WeatherData weatherData = new WeatherData(currentConditions);
//更新天氣情況
weatherData.setData(30, 150, 40);
//天氣情況變化
System.out.println("============天氣情況變化=============");
weatherData.setData(40, 160, 20);
}
}
【運(yùn)行】
***Today mTemperature: 30.0***
***Today mPressure: 150.0***
***Today mHumidity: 40.0***
============天氣情況變化=============
***Today mTemperature: 40.0***
***Today mPressure: 160.0***
***Today mHumidity: 20.0***
Process finished with exit code 0
問題分析
當(dāng)添加其他第三方網(wǎng)站時(shí),需要修改WeatherData類,不符合開閉原則,不利于維護(hù)
改進(jìn):推薦使用觀察者模式
介紹
基礎(chǔ)介紹
- 觀察者模式的工作原理:觀察者到觀察對象中進(jìn)行注冊,當(dāng)觀察對象的狀態(tài)發(fā)生改變時(shí),就通知已經(jīng)注冊的觀察者,觀察者可以被從觀察對象中移除
登場角色
-
Subject(觀察對象)
:Subject角色定義了注冊、刪除觀察者的方法,還可以聲明“獲取現(xiàn)在的狀態(tài)”的方法 -
ConcreteSubject(具體的觀察對象)
:當(dāng)自身狀態(tài)發(fā)生變化后,它會(huì)通知所有已經(jīng)注冊的Observer角色 -
Observer(觀察者)
:負(fù)責(zé)接收來自Subject角色狀態(tài)變化的通知,聲明了update方法來接收通知 -
ConcreteObserver(具體的觀察者)
:當(dāng)它的update方法被調(diào)用后,會(huì)去獲取要觀察的對象的最新狀態(tài)
案例實(shí)現(xiàn)
案例一
類圖
實(shí)現(xiàn)
【Subject:觀察對象接口】
package com.atguigu.observer.improve;
/**
* 接口, 讓W(xué)eatherData 來實(shí)現(xiàn)
*/
public interface Subject {
/**
* 觀察者注冊
* @param o
*/
public void registerObserver(Observer o);
/**
* 移除觀察者
* @param o
*/
public void removeObserver(Observer o);
/**
* 通知所有觀察者
*/
public void notifyObservers();
}
【Observer:觀察者接口】
package com.atguigu.observer.improve;
/**
* 觀察者接口
*/
public interface Observer {
/**
* 更新數(shù)據(jù)
*
* @param temperature
* @param pressure
* @param humidity
*/
public void update(float temperature, float pressure, float humidity);
}
【W(wǎng)eatherData:具體觀察對象】
package com.atguigu.observer.improve;
import java.util.ArrayList;
/**
* 類是核心
* 1. 包含最新的天氣情況信息
* 2. 含有 觀察者集合,使用ArrayList管理
* 3. 當(dāng)數(shù)據(jù)有更新時(shí),就主動(dòng)的調(diào)用ArrayList, 通知所有的(接入方)就看到最新的信息
*
* @author Administrator
*/
public class WeatherData implements Subject {
private float temperature;
private float pressure;
private float humidity;
/**
* 觀察者集合
*/
private ArrayList<Observer> observers;
/**
* 構(gòu)造方法
*/
public WeatherData() {
// 創(chuàng)建新的集合
observers = new ArrayList<Observer>();
}
public float getTemperature() {
return temperature;
}
public float getPressure() {
return pressure;
}
public float getHumidity() {
return humidity;
}
public void dataChange() {
//調(diào)用 接入方的 update
notifyObservers();
}
//當(dāng)數(shù)據(jù)有更新時(shí),就調(diào)用 setData
public void setData(float temperature, float pressure, float humidity) {
this.temperature = temperature;
this.pressure = pressure;
this.humidity = humidity;
//調(diào)用dataChange, 將最新的信息 推送給 接入方 currentConditions
dataChange();
}
/**
* 注冊一個(gè)觀察者
* @param o
*/
@Override
public void registerObserver(Observer o) {
observers.add(o);
}
/**
* 移除一個(gè)觀察者
* @param o
*/
@Override
public void removeObserver(Observer o) {
if (observers.contains(o)) {
observers.remove(o);
}
}
/**
* 遍歷所有的觀察者,并通知
*/
@Override
public void notifyObservers() {
for (int i = 0; i < observers.size(); i++) {
observers.get(i).update(this.temperature, this.pressure, this.humidity);
}
}
}
【CurrentConditions:具體觀察者】
package com.atguigu.observer.improve;
public class CurrentConditions implements Observer {
/**
* 溫度
*/
private float temperature;
/**
* 氣壓
*/
private float pressure;
/**
* 濕度
*/
private float humidity;
/**
* 更新 天氣情況,是由 WeatherData 來調(diào)用,我使用推送模式
*
* @param temperature
* @param pressure
* @param humidity
*/
public void update(float temperature, float pressure, float humidity) {
this.temperature = temperature;
this.pressure = pressure;
this.humidity = humidity;
display();
}
/**
* 顯示
*/
public void display() {
System.out.println("***Today mTemperature: " + temperature + "***");
System.out.println("***Today mPressure: " + pressure + "***");
System.out.println("***Today mHumidity: " + humidity + "***");
}
}
【BaiduSite:具體觀察者】
package com.atguigu.observer.improve;
/**
* 百度網(wǎng)站
*/
public class BaiduSite implements Observer {
private float temperature;
private float pressure;
private float humidity;
/**
* 更新天氣情況,是由 WeatherData 來調(diào)用,我使用推送模式
*
* @param temperature
* @param pressure
* @param humidity
*/
public void update(float temperature, float pressure, float humidity) {
this.temperature = temperature;
this.pressure = pressure;
this.humidity = humidity;
display();
}
/**
* 顯示
*/
public void display() {
System.out.println("===百度網(wǎng)站====");
System.out.println("***百度網(wǎng)站 氣溫 : " + temperature + "***");
System.out.println("***百度網(wǎng)站 氣壓: " + pressure + "***");
System.out.println("***百度網(wǎng)站 濕度: " + humidity + "***");
}
}
【主類】
package com.atguigu.observer.improve;
public class Client {
public static void main(String[] args) {
//創(chuàng)建一個(gè)WeatherData
WeatherData weatherData = new WeatherData();
//創(chuàng)建觀察者
CurrentConditions currentConditions = new CurrentConditions();
BaiduSite baiduSite = new BaiduSite();
//注冊到weatherData
weatherData.registerObserver(currentConditions);
weatherData.registerObserver(baiduSite);
//測試
System.out.println("通知各個(gè)注冊的觀察者, 看看信息");
weatherData.setData(10f, 100f, 30.3f);
System.out.println();
System.out.println("----- 移除一個(gè)觀察者 -----");
weatherData.removeObserver(currentConditions);
//測試
System.out.println();
System.out.println("通知各個(gè)注冊的觀察者, 看看信息");
weatherData.setData(10f, 100f, 30.3f);
}
}
【運(yùn)行】
通知各個(gè)注冊的觀察者, 看看信息
***Today mTemperature: 10.0***
***Today mPressure: 100.0***
***Today mHumidity: 30.3***
===百度網(wǎng)站====
***百度網(wǎng)站 氣溫 : 10.0***
***百度網(wǎng)站 氣壓: 100.0***
***百度網(wǎng)站 濕度: 30.3***
----- 移除一個(gè)觀察者 -----
通知各個(gè)注冊的觀察者, 看看信息
===百度網(wǎng)站====
***百度網(wǎng)站 氣溫 : 10.0***
***百度網(wǎng)站 氣壓: 100.0***
***百度網(wǎng)站 濕度: 30.3***
Process finished with exit code 0
分析
- 使用該模式,拓展方便,比如需要新增一個(gè)觀察者,只需要實(shí)現(xiàn)一個(gè)具體的觀察者類,然后在觀察對象中注冊即可
案例二
類圖
實(shí)現(xiàn)
【觀察者接口】
package com.atguigu.observer.Sample;
public interface Observer {
public abstract void update(NumberGenerator generator);
}
【被觀察對象抽象類】
package com.atguigu.observer.Sample;
import java.util.ArrayList;
import java.util.Iterator;
/**
* 用來生成數(shù)值的抽象類
*/
public abstract class NumberGenerator {
/**
* 保存Observer們
*/
private ArrayList observers = new ArrayList();
/**
* 注冊O(shè)bserver
*
* @param observer
*/
public void addObserver(Observer observer) {
observers.add(observer);
}
/**
* 刪除Observer
*
* @param observer
*/
public void deleteObserver(Observer observer) {
observers.remove(observer);
}
/**
* 向Observer發(fā)送通知
*/
public void notifyObservers() {
Iterator it = observers.iterator();
while (it.hasNext()) {
Observer o = (Observer) it.next();
o.update(this);
}
}
/**
* 獲取數(shù)值
*
* @return
*/
public abstract int getNumber();
/**
* 生成數(shù)值
*/
public abstract void execute();
}
【具體的被觀察對象】
package com.atguigu.observer.Sample;
import java.util.Random;
/**
* 具體的被觀察對象
*/
public class RandomNumberGenerator extends NumberGenerator {
/**
* 隨機(jī)數(shù)生成器
*/
private Random random = new Random();
/**
* 當(dāng)前數(shù)值
*/
private int number;
/**
* 獲取當(dāng)前數(shù)值
* @return
*/
public int getNumber() {
return number;
}
public void execute() {
for (int i = 0; i < 20; i++) {
// 生成隨機(jī)數(shù)
number = random.nextInt(50);
// 通知觀察者
notifyObservers();
}
}
}
【數(shù)值顯示觀察者】
package com.atguigu.observer.Sample;
/**
* 使用數(shù)字形式顯示觀察到的數(shù)值
*/
public class DigitObserver implements Observer {
public void update(NumberGenerator generator) {
System.out.println("DigitObserver:" + generator.getNumber());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
}
}
【圖像顯示觀察者】
package com.atguigu.observer.Sample;
/**
* 使用圖像顯示觀察到的數(shù)值
*/
public class GraphObserver implements Observer {
public void update(NumberGenerator generator) {
System.out.print("GraphObserver:");
int count = generator.getNumber();
for (int i = 0; i < count; i++) {
System.out.print("*");
}
System.out.println("");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
}
}
【主類】
package com.atguigu.observer.Sample;
public class Main {
public static void main(String[] args) {
NumberGenerator generator = new RandomNumberGenerator();
Observer observer1 = new DigitObserver();
Observer observer2 = new GraphObserver();
generator.addObserver(observer1);
generator.addObserver(observer2);
generator.execute();
}
}
【運(yùn)行】
DigitObserver:2
GraphObserver:**
DigitObserver:7
GraphObserver:*******
DigitObserver:6
GraphObserver:******
DigitObserver:26
GraphObserver:**************************
DigitObserver:16
GraphObserver:****************
DigitObserver:6
GraphObserver:******
DigitObserver:28
GraphObserver:****************************
DigitObserver:10
GraphObserver:**********
DigitObserver:6
GraphObserver:******
DigitObserver:10
GraphObserver:**********
DigitObserver:20
GraphObserver:********************
DigitObserver:14
GraphObserver:**************
DigitObserver:21
GraphObserver:*********************
DigitObserver:38
GraphObserver:**************************************
DigitObserver:31
GraphObserver:*******************************
DigitObserver:34
GraphObserver:**********************************
DigitObserver:19
GraphObserver:*******************
DigitObserver:30
GraphObserver:******************************
DigitObserver:0
GraphObserver:
DigitObserver:1
GraphObserver:*
Process finished with exit code 0
觀察者模式在JDK源碼的應(yīng)用
Observable直接就是類,沒有實(shí)現(xiàn)Subject接口,直接在類中實(shí)現(xiàn)管理Observer的核心方法,當(dāng)然,可以寫更多的子類來集成Observable以便實(shí)現(xiàn)更多的操作文章來源:http://www.zghlxwxcb.cn/news/detail-625127.html
文章來源地址http://www.zghlxwxcb.cn/news/detail-625127.html
總結(jié)
- 可替換性強(qiáng):被觀察對象中的觀察者集合的觀察者類型是Observer角色,可以接收各種各樣的具體Observer角色,而且可以統(tǒng)一調(diào)用他們的update方法;Observer的update方法接收的數(shù)據(jù)類型也是Subject,在update方法里面也可以統(tǒng)一調(diào)用方法來獲取具體被觀察對象的屬性
- update方法所需要的參數(shù)非常靈活,可以自己按照需求來定義
- 觀察者模式并非主動(dòng)去觀察,而是被觀測對象主動(dòng)去通知,稱為發(fā)布——訂閱模式可能更加貼切
文章說明
- 本文章為本人學(xué)習(xí)尚硅谷的學(xué)習(xí)筆記,文章中大部分內(nèi)容來源于尚硅谷視頻(點(diǎn)擊學(xué)習(xí)尚硅谷相關(guān)課程),也有部分內(nèi)容來自于自己的思考,發(fā)布文章是想幫助其他學(xué)習(xí)的人更方便地整理自己的筆記或者直接通過文章學(xué)習(xí)相關(guān)知識(shí),如有侵權(quán)請聯(lián)系刪除,最后對尚硅谷的優(yōu)質(zhì)課程表示感謝。
- 本人還同步閱讀《圖解設(shè)計(jì)模式》書籍(圖解設(shè)計(jì)模式/(日)結(jié)城浩著;楊文軒譯–北京:人民郵電出版社,2017.1),進(jìn)而綜合兩者的內(nèi)容,讓知識(shí)點(diǎn)更加全面
到了這里,關(guān)于【設(shè)計(jì)模式——學(xué)習(xí)筆記】23種設(shè)計(jì)模式——觀察者模式Observer(原理講解+應(yīng)用場景介紹+案例介紹+Java代碼實(shí)現(xiàn))的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!