設計模式學習筆記(二:觀察者模式)


1.1概述

    在許多設計中,經常涉及多個對象都對一個特殊對象中的數據變化感興趣,而且這多個對象都希望跟蹤那個特殊對象中的數據變化,在這樣的情況下就可以使用觀察者模式。

    例如,某些尋找工作的人對“求職中心”的職業需求信息的變化非常關心,很想追蹤“求職中心”中職業需求信息的變化。一位想知道“求職中心”職業需求信息變化的人需要成為“求職中心”的求職者,即讓求職中心把自己登記到求職中心的“求職者”列表中,當一個人成為求職中心的求職者后,求職中心就會及時通知他最新的職業需求信息。

    觀察者模式是關於多個對象想知道一個對象中數據變化情況的一種成熟的模式。觀察者模式中有一個稱作“主題”的對象和若干個稱作“觀察者”的對象,“主題”和“觀察者”間是一種一對多的依賴關系,當“主題”的狀態發生變化時,所有“觀察者”都得到通知。前面所述的“求職中心”相當於觀察者模式的一個具體“主題”;每個“求職者”相當於觀察者模式中的一個具體“觀察者”。

 

1.2模式的結構

    觀察者模式的結構中包含四種角色:

(1)主題(Subject):主題是一個接口,該接口規定了具體主題需要實現的方法,比如,添加、刪除觀察者以及通知觀察者更新數據的方法。

(2)觀察者(Observer):觀察者是一個接口,該接口規定了具體觀察者用來更新數據的方法。

(3)具體主題(ConcreteSubject):具體主題是實現主題接口類的一個實例,該實例包含有可以經常發生變化的數據。具體主題需使用一個集合,比如ArrayList,存放觀察者的引用,以便數據變化時通知具體觀察者。

(4)具體觀察者(ConcreteObserver):具體觀察者是實現觀察者接口類的一個實例。具體觀察者包含有可以存放具體主題引用的主題接口變量,以便具體觀察者讓具體主題將自己的引用添加到具體主題的集合中,使自己成為它的觀察者,或讓這個具體主題將自己從具體主題的集合中刪除,使自己不再是它的觀察者。

    觀察者模式結構的類圖如下所示:

 

 

 

1.3觀察者模式的優點

(1)具體主題和具體觀察者是松耦合關系。由於主題接口僅僅依賴於觀察者接口,因此具體主題只是知道它的觀察者是實現觀察者接口的某個類的實例,但不需要知道具體是哪個類。同樣,由於觀察者僅僅依賴於主題接口,因此具體觀察者只是知道它依賴的主題是實現主題接口的某個類的實例,但不需要知道具體是哪個類。

(2)觀察者模式滿足“開-閉原則”。主題接口僅僅依賴於觀察者接口,這樣,就可以讓創建具體主題的類也僅僅是依賴於觀察者接口,因此,如果增加新的實現觀察者接口的類,不必修改創建具體主題的類的代碼。。同樣,創建具體觀察者的類僅僅依賴於主題接口,如果增加新的實現主題接口的類,也不必修改創建具體觀察者類的代碼。

 

1.4適合使用觀察者模式的情景

(1)當一個對象的數據更新時需要通知其他對象,但這個對象又不希望和被通知的那些對象形成緊耦合。

(2)當一個對象的數據更新時,這個對象需要讓其他對象也各自更新自己的數據,但這個對象不知道具體有多少對象需要更新數據。

 

 

1.5觀察者模式的應用

下面通過一個簡單的問題來描述觀察者模式中所涉及的各個角色,這個簡單的問題是:有一個大學畢業生和一個歸國留學者都希望能及時知道“求職中心”最炫的職業需求信息。

首先看一下本實例構建框架具體類和1.2模式的結構中類圖的對應關系,如下圖3所示:

圖3  具體編寫類及接口與類圖對應關系

(1)主題(Subject

本問題中,主題接口Subject規定了具體主題需要實現的添加、刪除觀察者以及通知觀察者更新數據的方法。其代碼如下:

package com.liuzhen.two_observer;

public interface Subject {
    public void addObserver(Observer o);
    public void deleteObserver(Observer o);
    public void notifyObservers();
}

 

(2)觀察者(Observer

觀察者是一個接口,該接口規定了具體觀察者用來更新數據的方法。對於本問題,觀察者規定的方法是:hearTelephone()(相當於觀察者模式類圖中的update()方法),即要求具體觀察者都通過實現hearTelephone()方法(模擬接聽電話)來更新數據。其代碼如下:

package com.liuzhen.two_observer;

public interface Observer {
    public void hearTelephone(String heardMess);
}

 

(3)具體主題(ConcreteSubject

具體主題維護着一個String字符串,用來表示“求職中心”的職業需求信息,當該String字符串發生變化時,具體主題遍歷存放觀察者引用集合。具體代碼如下:

package com.liuzhen.two_observer;

import java.util.ArrayList;

public class SeekJobCenter implements Subject{
    String mess;
    boolean changed;
    ArrayList<Observer> personList;     //存放觀察者引用的數組線性表
    SeekJobCenter(){
        personList = new ArrayList<Observer>();
        mess = "";
        changed = false;
    }
    public void addObserver(Observer o){
        if(!(personList.contains(o))){
            personList.add(o);         //把觀察者的引用添加到數組線性表
        }
    }
    public void deleteObserver(Observer o){
        if(personList.contains(o)){
            personList.remove(o);     //把觀察者的引用移除數組線性表
        }
    }
    public void notifyObservers(){
        if(changed){                 //通知所有的觀察者
            for(int i = 0;i < personList.size();i++){
                Observer observer = personList.get(i);
                observer.hearTelephone(mess);   //讓所有的觀察者接聽電話
            }
        }
    } 
    public void getNewMess(String str){  //判斷信息是否是新發布的
        if(str.equals(mess)){
            changed = false;
        }
        else{
            mess = str;
            changed = true;
        }
    }
}

 

(4)具體觀察者(ConcreteObserver

本問題中,實現觀察者接口Observer的類有兩個:一個UniversityStudent類,另一個是HaiGuiUniversityStudent類的實例調用hearTelephone(String heardMess)方法時,會將參數引用的字符串保存到一個文件中。HaiGui類的實例調用hearTelephone(String heardMess)方法時,如果參數引用的字符串中包含有“程序員”或軟件,就將信息保存到一個文件中。

UniversityStudent類代碼如下:

package com.liuzhen.two_observer;

import java.io.*;


public class UniversityStudent implements Observer {
    Subject subject;
    File myFile;
    UniversityStudent(Subject subject,String fileName){
        this.subject = subject;
        subject.addObserver(this);       //使當前實例成為subject所使用的具體主題的觀察者
        myFile = new File(fileName);
    }
    public void hearTelephone(String heardMess) {
        try{
            RandomAccessFile out1 = new RandomAccessFile(myFile,"rw");
            out1.seek(out1.length());
            byte[] b = heardMess.getBytes();
            out1.write(b);       //更新文件中的內容
            System.out.print("我是一個大學生,");
            System.out.println("我向文件"+myFile.getName()+"寫入如下內容:");
            System.out.println(heardMess);
        }
        catch(IOException exp){
            System.out.println(exp.toString());
        }

    }

}

HaiGui類代碼如下:

package com.liuzhen.two_observer;

import java.io.*;

public class HaiGui implements Observer {
    Subject subject;
    File myFile;
    HaiGui(Subject subject , String fileName){
        this.subject = subject;
        subject.addObserver(this);      //使當前實例成為subject所引用的具體主題的觀察者
        myFile = new File(fileName);
    }
    
    public void hearTelephone(String heardMess) {
        try{
            boolean boo = heardMess.contains("java程序員")||heardMess.contains("軟件");
            if(boo){
                RandomAccessFile out = new RandomAccessFile(myFile,"rw");
                out.seek(out.length());
                byte[] b = heardMess.getBytes();
                out.write(b);
                System.out.print("我是一個海歸");
                System.out.println("我向文件"+myFile.getName()+"寫入如下內容:");
                System.out.println(heardMess);
            }
            else{
                System.out.println("我是海歸,這次的信息中沒有我需要的信息");
            }
        }
        catch(IOException exp){
            System.out.println(exp.toString());
        }
    }

}

 

(5)具體調用實現

    通過TwoApllication類來具體實現上述相關類和接口,來實現觀察者模式的運用,其代碼如下:

package com.liuzhen.two_observer;

public class TwoApplication {

    public static void main(String[] args) {
        SeekJobCenter center = new SeekJobCenter();     //具體主題center
        UniversityStudent zhanglin = new UniversityStudent(center,"A.txt");  //具體觀察者zhanglin
        HaiGui wanghao = new HaiGui(center,"B.txt");    //具體觀察者wanghao
        center.getNewMess("騰輝公司需要10個Java程序員。");      //具體主題給出新信息
        center.notifyObservers();                       //具體主題通知信息
        center.getNewMess("海景公司需要8個動畫設計師。");
        center.notifyObservers();
        center.getNewMess("仁海公司需要9個電工。");
        center.notifyObservers();
        center.getNewMess("仁海公司需要9個電工。");            //信息不是新的
        center.notifyObservers();                       //觀察者不會執行更新操作
    }

}

 

運行結果:

我是一個大學生,我向文件A.txt寫入如下內容:
騰輝公司需要10個Java程序員。
我是海歸,這次的信息中沒有我需要的信息
我是一個大學生,我向文件A.txt寫入如下內容:
海景公司需要8個動畫設計師。
我是海歸,這次的信息中沒有我需要的信息
我是一個大學生,我向文件A.txt寫入如下內容:
仁海公司需要9個電工。
我是海歸,這次的信息中沒有我需要的信息

 

參考資料:

      1.Java設計模式/耿祥義,張躍平著.——北京:清華大學出版社,2009.5


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM