設(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)行評(píng)分。
- 老師和學(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



















 
 
 






 
 
 
 