設(shè)計(jì)模式系列—觀察者模式
前言
- 23種設(shè)計(jì)模式速記
- 單例(singleton)模式
- 工廠方法(factory method)模式
- 抽象工廠(abstract factory)模式
- 建造者/構(gòu)建器(builder)模式
- 原型(prototype)模式
- 享元(flyweight)模式
- 外觀(facade)模式
- 適配器(adapter)模式
- 裝飾(decorator)模式
- 持續(xù)更新中......
23種設(shè)計(jì)模式快速記憶的請看上面第一篇,本篇和大家一起來學(xué)習(xí)觀察者模式相關(guān)內(nèi)容。
模式定義
定義了對象之間的一對多依賴,讓多個(gè)觀察者對象同時(shí)監(jiān)聽某一個(gè)主題對象,當(dāng)主題對象發(fā)生變化時(shí),它的所有依賴者都會(huì)收到通知并更新。這種模式有時(shí)又稱作發(fā)布-訂閱模式、模型-視圖模式,它是對象行為型模式。
觀察者模式是對象之間一對多的一種模式,被依賴的對象是Subject,依賴的對象是Observer,Subject通知Observer變化,Subject為1,Observer為多。
解決的問題
一個(gè)對象狀態(tài)改變給其他對象通知的問題,而且要考慮到易用和低耦合,保證高度的協(xié)作。
模式組成
實(shí)例說明
實(shí)例概況
某天下午班主任通知某班學(xué)生和老師將要聽一節(jié)課,以此來對老師的授課質(zhì)量進(jìn)行評分。
- 老師和學(xué)生收到后開始安排相關(guān)的課程;
- 上課期間老師和班主任通過觀察學(xué)生的神情來預(yù)判課程的講的好壞
- 老師觀察到學(xué)生皺眉頭可以適當(dāng)調(diào)節(jié)課程氣氛
- 班主任觀察到學(xué)生課堂氛圍好轉(zhuǎn),給予高分
- 課程結(jié)束后,班主任和老師回到辦公室,一起回顧這節(jié)開心的課程。
使用步驟
步驟1:構(gòu)建一個(gè)課程實(shí)體類
- class Course {
- // 上課時(shí)間:time
- private Date time;
- // 上課地點(diǎn):place
- private String place;
- // 上課內(nèi)容:content
- private String content;
- // 省略get/set...
- public Course() {
- }
- public Course(Date time, String place, String content) {
- this.time = time;
- this.place = place;
- this.content = content;
- }
- }
步驟2:構(gòu)建一個(gè)發(fā)現(xiàn)者的抽象類以及相關(guān)的實(shí)現(xiàn)類,老師和班主任都分別繼承了該接口并重寫相關(guān)方法
- abstract class Observer {
- abstract void update(Object args);
- public Observer(String identity) {
- this.identity = identity;
- }
- private String identity;
- public String getIdentity() {
- return identity;
- }
- public void setIdentity(String identity) {
- this.identity = identity;
- }
- }
步驟3:創(chuàng)建一個(gè)具體的觀察者角色,老師拿著教材開始來上課
- /**
- * 老師類
- * - 觀察者之一
- * - 觀察學(xué)生的上課情況
- */
- class TeacherObserver extends Observer {
- private Course course;
- @Override
- public void update(Object args) {
- DateFormat df = DateFormat.getTimeInstance(DateFormat.LONG, Locale.CHINA);
- System.out.println("我是王老師,正在講課中...");
- course = new Course(new Date(), "A棟教學(xué)樓", "高等數(shù)學(xué)");
- System.out.println("今天上課時(shí)間:" + df.format(course.getTime()) + " 地點(diǎn):" + course.getPlace() + " 上課內(nèi)容:" + course.getContent());
- }
- public TeacherObserver(String identity) {
- super(identity);
- }
- }
步驟4:創(chuàng)建一個(gè)具體的觀察者角色,班主任來聽課
- /**
- * 班主任來聽課
- * - 觀察者之一
- * - 觀察學(xué)生的上課情況
- */
- class HeadTeacherObserver extends Observer {
- @Override
- public void update(Object args) {
- System.out.println("我是班主任來聽課了,正在檢查課程質(zhì)量...");
- System.out.println("學(xué)生反饋課程質(zhì)量為:" + args);
- }
- public HeadTeacherObserver(String identity) {
- super(identity);
- }
- }
步驟5:創(chuàng)建被觀察者抽象主題角色
- /**
- * 主體類
- * - 模擬被觀察者主體
- */
- abstract class Subject {
- /**
- * 修改通知
- */
- abstract void doNotify();
- /**
- * 添加被觀察者
- */
- abstract void addObservable(Observer o);
- /**
- * 移除被觀察者
- */
- abstract void removeObservable(Observer o);
- }
步驟6:創(chuàng)建具體的被觀察者主體角色,學(xué)生主體為被觀察對象
- /**
- * 學(xué)生主體
- * - 被觀察的對象
- */
- class StudentSubject extends Subject {
- /**
- * 上課狀態(tài)
- */
- private String state;
- public String getState() {
- return state;
- }
- public void setState(String state) {
- this.state = state;
- }
- private List<Observer> observableList = new ArrayList<>();
- @Override
- public void doNotify() {
- for (Observer observer : observableList) {
- observer.update(state);
- }
- }
- @Override
- public void addObservable(Observer observable) {
- observableList.add(observable);
- }
- @Override
- public void removeObservable(Observer observable) {
- try {
- if (observable == null) {
- throw new Exception("要移除的被觀察者不能為空");
- } else {
- if (observableList.contains(observable) ) {
- System.out.println("下課了,"+observable.getIdentity()+" 已回到辦公室");
- observableList.remove(observable);
- }
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
步驟7:開始上課,開始記錄報(bào)告
- /**
- * 觀察者模式
- */
- public class ObserverPattern {
- public static void main(String[] args) {
- // 創(chuàng)建學(xué)生主體
- StudentSubject studentSubject = new StudentSubject();
- // 創(chuàng)建觀察者老師
- TeacherObserver teacherObversable = new TeacherObserver("王老師");
- // 創(chuàng)建觀察者班主任
- HeadTeacherObserver headTeacherObserver = new HeadTeacherObserver("班主任");
- // 學(xué)生反映上課狀態(tài)
- studentSubject.setState("講的不錯(cuò),很好!");
- studentSubject.addObservable(teacherObversable);
- studentSubject.addObservable(headTeacherObserver);
- // 開始上課
- studentSubject.doNotify();
- // 上課結(jié)束
- studentSubject.removeObservable(headTeacherObserver);
- studentSubject.removeObservable(teacherObversable);
- }
- }
輸出結(jié)果
- 我是王老師,正在講課中...
- 今天上課時(shí)間:下午11時(shí)57分01秒 地點(diǎn):A棟教學(xué)樓 上課內(nèi)容:高等數(shù)學(xué)
- 我是班主任來聽課了,正在檢查課程質(zhì)量...
- 學(xué)生反饋課程質(zhì)量為:講的不錯(cuò),很好!
- 下課了,班主任 已回到辦公室
- 下課了,王老師 已回到辦公室
優(yōu)點(diǎn)
- 符合開閉原則
- 降低了目標(biāo)與觀察者之間的耦合關(guān)系,兩者之間是抽象耦合關(guān)系;
- 目標(biāo)與觀察者之間建立了一套觸發(fā)機(jī)制。
缺點(diǎn)
- 目標(biāo)與觀察者之間的依賴關(guān)系并沒有完全解除,而且有可能出現(xiàn)循環(huán)引用;
- 當(dāng)觀察者對象很多時(shí),通知的發(fā)布會(huì)花費(fèi)很多時(shí)間,影響程序的效率。
應(yīng)用場景
當(dāng)更改一個(gè)對象的狀態(tài)可能需要更改其他對象,并且實(shí)際的對象集事先未知或動(dòng)態(tài)更改時(shí),請使用觀察者模式。
注意事項(xiàng): 1、JAVA 中已經(jīng)有了對觀察者模式的支持類。 2、避免循環(huán)引用。 3、如果順序執(zhí)行,某一觀察者錯(cuò)誤會(huì)導(dǎo)致系統(tǒng)卡殼,一般采用異步方式。
源碼中的應(yīng)用
- #JDK:
- java.util.Observable
- #Spring:
- org.springframework.context.ApplicationListener
Observable源碼分析
Observable接口
- public interface Observer {
- void update(Observable o, Object arg);
- }
Observable類
- public class Observable {
- private Vector<Observer> obs;
- //添加觀察者
- public synchronized void addObserver(Observer o) {
- if (o == null)
- throw new NullPointerException();
- if (!obs.contains(o)) {
- obs.addElement(o);
- }
- }
- //刪除觀察者
- public synchronized void deleteObserver(Observer o) {
- obs.removeElement(o);
- }
- //通知所有觀察者
- public void notifyObservers() {
- notifyObservers(null);
- }
- public void notifyObservers(Object arg) {
- /*
- * a temporary array buffer, used as a snapshot of the state of
- * current Observers.
- */
- Object[] arrLocal;
- synchronized (this) {
- /* We don't want the Observer doing callbacks into
- * arbitrary code while holding its own Monitor.
- * The code where we extract each Observable from
- * the Vector and store the state of the Observer
- * needs synchronization, but notifying observers
- * does not (should not). The worst result of any
- * potential race-condition here is that:
- * 1) a newly-added Observer will miss a
- * notification in progress
- * 2) a recently unregistered Observer will be
- * wrongly notified when it doesn't care
- */
- if (!changed)
- return;
- arrLocal = obs.toArray();
- clearChanged();
- }
- for (int i = arrLocal.length-1; i>=0; i--)
- ((Observer)arrLocal[i]).update(this, arg);
- }
- }
使用JDK提供的類實(shí)現(xiàn)觀察者模式
通過使用JDK的類來實(shí)現(xiàn)上面實(shí)例相關(guān)的觀察模式,自帶的觀察者的類有Observer接口和Observable類,使用這兩個(gè)類中的方法可以很好的完成觀察者模式,而且JDK幫我們做了相關(guān)的加鎖操作,保證了線程安全,整體來說會(huì)對我們上面的例子進(jìn)行改進(jìn)和簡化操作,代碼如下:
- package com.niuh.designpattern.observer.v2;
- import java.text.DateFormat;
- import java.util.Date;
- import java.util.Locale;
- import java.util.Observable;
- import java.util.Observer;
- /**
- * 觀察者模式
- */
- public class ObserverPattern {
- // 步驟6:開始上課,開始記錄報(bào)告
- public static void main(String[] args) {
- // 創(chuàng)建學(xué)生主體
- StudentSubject studentSubject = new StudentSubject();
- // 創(chuàng)建觀察者老師
- TeacherObserver teacherObversable = new TeacherObserver();
- // 創(chuàng)建觀察者班主任
- HeadTeacherObserver headTeacherObserver = new HeadTeacherObserver();
- // 學(xué)生反映上課狀態(tài)
- studentSubject.setState("講的不錯(cuò),很好!");
- studentSubject.addObserver(teacherObversable);
- studentSubject.addObserver(headTeacherObserver);
- // 開始上課
- studentSubject.doNotify();
- // 上課結(jié)束
- studentSubject.deleteObserver(headTeacherObserver);
- studentSubject.deleteObserver(teacherObversable);
- }
- }
- /**
- * 課程類
- */
- class Course {
- // 上課時(shí)間:time
- private Date time;
- // 上課地點(diǎn):place
- private String place;
- // 上課內(nèi)容:content
- private String content;
- public Date getTime() {
- return time;
- }
- public void setTime(Date time) {
- this.time = time;
- }
- public String getPlace() {
- return place;
- }
- public void setPlace(String place) {
- this.place = place;
- }
- public String getContent() {
- return content;
- }
- public void setContent(String content) {
- this.content = content;
- }
- public Course() {
- }
- public Course(Date time, String place, String content) {
- this.time = time;
- this.place = place;
- this.content = content;
- }
- }
- /**
- * 老師類
- * - 觀察者之一
- * - 觀察學(xué)生的上課情況
- */
- class TeacherObserver implements Observer {
- private Course course;
- @Override
- public void update(Observable o, Object arg) {
- DateFormat df = DateFormat.getTimeInstance(DateFormat.LONG, Locale.CHINA);
- System.out.println("我是王老師,正在講課中...");
- course = new Course(new Date(), "A棟教學(xué)樓", "高等數(shù)學(xué)");
- System.out.println("今天上課時(shí)間:" + df.format(course.getTime()) + " 地點(diǎn):" + course.getPlace() + " 上課內(nèi)容:" + course.getContent());
- }
- }
- /**
- * 班主任來聽課
- * - 觀察者之一
- * - 觀察學(xué)生的上課情況
- */
- class HeadTeacherObserver implements Observer {
- @Override
- public void update(Observable o, Object arg) {
- System.out.println("我是班主任來聽課了,正在檢查課程質(zhì)量...");
- System.out.println("學(xué)生反饋課程質(zhì)量為:" + arg);
- }
- }
- /**
- * 學(xué)生主體
- * - 被觀察的對象
- */
- class StudentSubject extends Observable {
- /**
- * 上課狀態(tài)
- */
- private String state;
- public String getState() {
- return state;
- }
- public void setState(String state) {
- this.state = state;
- }
- public void doNotify() {
- // 設(shè)置標(biāo)志
- this.setChanged();
- // 通知觀察者做出相應(yīng)動(dòng)作
- this.notifyObservers(state);
- }
- }
輸出結(jié)果:
- 我是班主任來聽課了,正在檢查課程質(zhì)量...
- 學(xué)生反饋課程質(zhì)量為:講的不錯(cuò),很好!
- 我是王老師,正在講課中...
- 今天上課時(shí)間:上午12時(shí)04分27秒 地點(diǎn):A棟教學(xué)樓 上課內(nèi)容:高等數(shù)學(xué)
PS:以上代碼提交在 Github :
https://github.com/Niuh-Study/niuh-designpatterns.git