一、基本介紹
觀察者模式是一種對象行為模式。它定義對象間的一種一對多的依賴關系,當一個對象的狀態發生改變時,所有依賴於它的對象都得到通知並被自動更新。在觀察者模式中,主題是通知的發布者,它發出通知時並不需要知道誰是它的觀察者,可以有任意數目的觀察者訂閱並接收通知。觀察者模式不僅被廣泛應用於軟件界面元素之間的交互,在業務對象之間的交互、權限管理等方面也有廣泛的應用。
—— 引用自百度百科
二、模式的定義與特點
觀察者(Observer)模式的定義:指多個對象間存在一對多的依賴關系,當一個對象的狀態發生改變時,所有依賴於它的對象都得到通知並被自動更新。這種模式有時又稱作發布-訂閱模式、模型-視圖模式,它是對象行為型模式。
- 降低了目標與觀察者之間的耦合關系,兩者之間是抽象耦合關系。
- 目標與觀察者之間建立了一套觸發機制。
它的主要缺點如下:
- 目標與觀察者之間的依賴關系並沒有完全解除,而且有可能出現循環引用。
- 當觀察者對象很多時,通知的發布會花費很多時間,影響程序的效率。
三、模式的結構與實現
實現觀察者模式時要注意具體目標對象和具體觀察者對象之間不能直接調用,否則將使兩者之間緊密耦合起來,這違反了面向對象的設計原則。
觀察者模式的主要角色如下:
四、具體實現步驟
相關場景描述:
某天的下午,班主任通知某班學生和老師將要聽一節課,以此來對老師的授課質量進行評分。老師和學生收到后開始安排相關的課程。上課期間老師和班主任通過觀察學生的神情來預判課程的講的好壞,老師觀察到學生皺眉頭可以適當調節課程氣氛,班主任觀察到學生課堂氛圍好轉,給與高分。課程結束后,班主任和老師回到辦公室,一起回顧這節開心的課程。
①使用自定義的方式實現觀察者模式
1.構建一個課程實體類(以下均省略Setter和Getter)
/**
* 課程類
* - 上課時間:time
* - 上課地點:place
* - 上課內容:content
*/
public class Course {
private Date time;
private String place;
private String content;
public Course() {
}
public Course(Date time, String place, String content) {
this.time = time;
this.place = place;
this.content = content;
}
}
2.構建一個發現者的抽象類以及相關的實現類,老師和班主任都分別繼承了該接口並重寫相關方法,
public abstract class Observer {
abstract void update(Object args);
public Observer(String identity) {
this.identity = identity;
}
private String identity;
}
老師拿着教材開始來上課:
/**
* 老師類
* - 觀察者之一
* - 觀察學生的上課情況
*/
public 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棟教學樓", "Java課程");
System.out.println("今天上課時間:"+df.format(course.getTime()) + " 地點:" + course.getPlace() + " 上課內容:" + course.getContent());
}
public TeacherObserver(String identity) {
super(identity);
}
}
班主任來聽課:
/**
* 班主任來聽課
* - 觀察者之一
* - 觀察學生的上課情況
*/
public class HeadTeacherObserver extends Observer {
@Override
public void update(Object args) {
System.out.println("我是班主任來聽課了,正在檢查課程質量...");
System.out.println("學生反饋課程質量為:" + args);
}
public HeadTeacherObserver(String identity) {
super(identity);
}
}
這時候輪到我們學生的主體登場了:
/**
* 主體類
* - 模擬被觀察者主體
*/
public abstract class Subject {
/**
* 修改通知
*/
abstract void doNotify();
/**
* 添加被觀察者
*/
abstract void addObservable(Observer o);
/**
* 移除被觀察者
*/
abstract void removeObservable(Observer o);
}
學生主體,被觀察的對象:
/**
* 學生主體
* - 被觀察的對象
*/
public class StudentSubject extends Subject {
/**
* 上課狀態
*/
private String 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();
}
}
}
那么我們焦急萬分等待課程終於如期進行了,開始記錄報告吧:
/**
* 課程報告
* - 一起回顧這節有意思的課
*/
public class CourseReport {
public static void main(String[] args) {
// 創建學生主體
StudentSubject studentSubject = new StudentSubject();
// 創建觀察者老師
TeacherObserver teacherObversable = new TeacherObserver("老師");
// 創建觀察者班主任
HeadTeacherObserver headTeacherObserver = new HeadTeacherObserver("班主任");
// 學生反映上課狀態
studentSubject.setState("(*^▽^*)講的不錯,很好,隨手點個關注和在看!");
studentSubject.addObservable(teacherObversable);
studentSubject.addObservable(headTeacherObserver);
// 開始上課
studentSubject.doNotify();
// 上課結束
studentSubject.removeObservable(headTeacherObserver);
studentSubject.removeObservable(teacherObversable);
}
}
回到辦公室的老師和班主任都眉開眼笑,學生對這節課也是很滿意,可以看到如下的報告回顧:
我是老師,正在講課中...
今天上課時間:下午03時00分00秒 地點:A棟教學樓 上課內容:Java課程
我是班主任來聽課了,正在檢查課程質量...
學生反饋課程質量為:(*^▽^*)講的不錯,很好,隨手點個關注和在看!
下課了,班主任 已回到辦公室
下課了,老師 已回到辦公室
②使用JDK提供的類實現觀察者模式
這里我們可以使用JDK的類來實現相關的觀察模式,自帶的觀察者的類有Observer接口和Observable類,使用這兩個類中的方法可以很好的完成觀察者模式,而且JDK幫我們做了相關的加鎖操作,保證了線程安全,整體來說會對我們上面的類進行改進和簡化操作。
我們主要需要改造的是被觀察的主體和實現觀察者的類,如下所示:
/**
* 老師類
* - 觀察者之一
* - 觀察學生的上課情況
*/
public 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棟教學樓", "Java課程");
System.out.println("今天上課時間:"+df.format(course.getTime()) + " 地點:" + course.getPlace() + " 上課內容:" + course.getContent());
}
}
/*************************************/
/**
* 班主任來聽課
* - 觀察者之一
* - 觀察學生的上課情況
*/
public class HeadTeacherObserver implements Observer {
@Override
public void update(Observable o, Object arg) {
System.out.println("我是班主任來聽課了,正在檢查課程質量...");
System.out.println("學生反饋課程質量為:" + arg);
}
}
/*************************************/
/**
* 學生主體
* - 被觀察的對象
*/
public class StudentObservable extends Observable {
/**
* 上課狀態
*/
private String state;
public void doNotify() {
// 設置標志
this.setChanged();
// 通知觀察者做出相應動作
this.notifyObservers(state);
}
}
/*************************************/
public class CourseReport {
public static void main(String[] args) {
// 創建學生主體
StudentObservable studentObservable = new StudentObservable();
// 創建觀察者老師
TeacherObserver teacherObversable = new TeacherObserver();
// 創建觀察者班主任
HeadTeacherObserver headTeacherObserver = new HeadTeacherObserver();
// 學生反映上課狀態
studentObservable.setState("(*^▽^*)講的不錯,很好,隨手點個關注和在看!");
studentObservable.addObserver(teacherObversable);
studentObservable.addObserver(headTeacherObserver);
// 開始上課
studentObservable.doNotify();
// 上課結束
studentObservable.deleteObserver(headTeacherObserver);
studentObservable.deleteObserver(teacherObversable);
}
}
運行效果如下:
我是老師,正在講課中...
今天上課時間:下午03時00分00秒 地點:A棟教學樓 上課內容:Java課程
我是班主任來聽課了,正在檢查課程質量...
學生反饋課程質量為:(*^▽^*)講的不錯,很好,隨手點個關注和在看!
划重點:
這里Observer內部只定義了一個方法,主要用於在被觀察的對象發出通知后做出相應的動作:
update(Observable o, Object arg);
Observable類中的方法有:
addObserver(添加觀察者)、deleteObserver(刪除觀察者)、notifyObservers(喚醒所有的觀察者,可帶入參數)、deleteObservers(刪除所有觀察者)、setChanged(改變標志為True)、clearChanged(改變標志為false)、hasChanged(查看標志狀態)、countObservers(統計觀察者個數)
這些方法中有些加了同步機制保證線程安全,我們可以根據需要使用提供的已有相關方法,增強代碼的復用性。有興趣的可以看下源碼的實現,這里就不再累述。
五、總結
觀察者模式的讓我們知道了在設計開發的時候一定要“多用組合,少用繼承”。
我們設計開發是應該是針對接口變成,而不針對實現編程。
這是一種創建松散耦合代碼的技術。它定義對象間 一種一對多的依賴關系,當一個對象的狀態發生改變時,所有依賴於它的對象都將得到通知。由主體和觀察者組成,主體負責發布事件,同時觀察者通過訂閱這些事件來觀察該主體。主體並不知道觀察者的任何事情,觀察者知道主體並能注冊事件的回調函數。