1. 程式人生 > 程式設計 >一文看懂觀察者模式及案例詳解

一文看懂觀察者模式及案例詳解

一、基本介紹

​ 觀察者模式是一種物件行為模式。它定義物件間的一種一對多的依賴關係,當一個物件的狀態發生改變時,所有依賴於它的物件都得到通知並被自動更新。在觀察者模式中,主題是通知的釋出者,它發出通知時並不需要知道誰是它的觀察者,可以有任意數目的觀察者訂閱並接收通知。觀察者模式不僅被廣泛應用於軟體介面元素之間的互動,在業務物件之間的互動、許可權管理等方面也有廣泛的應用。

​ —— 引用自百度百科

二、模式的定義與特點

​ 觀察者(Observer)模式的定義:指多個物件間存在一對多的依賴關係,當一個物件的狀態發生改變時,所有依賴於它的物件都得到通知並被自動更新。這種模式有時又稱作釋出-訂閱模式、模型-檢視模式,它是物件行為型模式。

  1. 降低了目標與觀察者之間的耦合關係,兩者之間是抽象耦合關係。
  2. 目標與觀察者之間建立了一套觸發機制。

它的主要缺點如下:

  • 目標與觀察者之間的依賴關係並沒有完全解除,而且有可能出現迴圈引用。
  • 當觀察者物件很多時,通知的釋出會花費很多時間,影響程式的效率。

三、模式的結構與實現

​ 實現觀察者模式時要注意具體目標物件和具體觀察者物件之間不能直接呼叫,否則將使兩者之間緊密耦合起來,這違反了面向物件的設計原則。 觀察者模式的主要角色如下:

KuPg2D.png

四、具體實現步驟

相關場景描述:

​ 某天的下午,班主任通知某班學生和老師將要聽一節課,以此來對老師的授課質量進行評分。老師和學生收到後開始安排相關的課程。上課期間老師和班主任通過觀察學生的神情來預判課程的講的好壞,老師觀察到學生皺眉頭可以適當調節課程氣氛,班主任觀察到學生課堂氛圍好轉,給與高分。課程結束後,班主任和老師回到辦公室,一起回顧這節開心的課程。

使用自定義的方式實現觀察者模式

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);
    }
}
複製程式碼

回到辦公室的老師和班主任都眉開眼笑,學生對這節課也是很滿意,可以看到如下的報告回顧:

我是老師,正在講課中...
今天上課時間:下午030000秒 地點: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(),"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);
    }
}

複製程式碼

執行效果如下:

我是老師,隨手點個關注和在看!

複製程式碼

劃重點:

這裡Observer內部只定義了一個方法,主要用於在被觀察的物件發出通知後做出相應的動作:

update(Observable o,Object arg);

Observable類中的方法有:

addObserver(新增觀察者)、deleteObserver(刪除觀察者)、notifyObservers(喚醒所有的觀察者,可帶入引數)、deleteObservers(刪除所有觀察者)、setChanged(改變標誌為True)、clearChanged(改變標誌為false)、hasChanged(檢視標誌狀態)、countObservers(統計觀察者個數)

這些方法中有些加了同步機制保證執行緒安全,我們可以根據需要使用提供的已有相關方法,增強程式碼的複用性。有興趣的可以看下原始碼的實現,這裡就不再累述。

五、總結

觀察者模式的讓我們知道了在設計開發的時候一定要“多用組合,少用繼承”。

我們設計開發是應該是針對介面變成,而不針對實現程式設計。

這是一種建立鬆散耦合程式碼的技術。它定義物件間 一種一對多的依賴關係,當一個物件的狀態發生改變時,所有依賴於它的物件都將得到通知。由主體和觀察者組成,主體負責釋出事件,同時觀察者通過訂閱這些事件來觀察該主體。主體並不知道觀察者的任何事情,觀察者知道主體並能註冊事件的回撥函式。