設計模式 - 觀察者模式

當對象間存在一對多關係時,則使用觀察者模式(Observer Pattern)。好比,當一個對象被修改時,則會自動通知它的依賴對象。觀察者模式屬於行爲型模式。java

意圖

定義對象間的一種一對多的依賴關係,當一個對象的狀態發生改變時,全部依賴於它的對象都獲得通知並被自動更新。異步

通常實現

觀察者模式使用四個類 Subject(通用抽象基類,不包含狀態)、ConcreteSubject(帶特定狀態的Subject)、Observer (通用接口,不包含狀態)和 ConcreteObserver (帶特定的Observer )。ide

Subject基類:主要做用是抽象了被觀察目標的通用操做,好比添加觀察者、刪除觀察者、通知全部的觀察者。測試

package com.dotleo.observer;

import java.util.ArrayList;
import java.util.List;

/**
 * 目標對象,它知道觀察它的觀察者,並提供註冊(添加)和刪除觀察者的接口
 *
 * @author LiuFei
 * @create 2017-12-03 19:53
 */
public class Subject {

    // 用來保存註冊的觀察者對象
    private List<Observer> observers = new ArrayList<Observer>();

    // attach detach notifyObservers
    // 綁定觀察者
    public void attach(Observer observer) {
        observers.add(observer);
    }

    // 刪除觀察者
    public void detach(Observer observer) {
        observers.remove(observer);
    }

    // 通知全部觀察者
    protected void notifyObServers() {
        for (Observer observer : observers) {
            observer.update(this);
        }
    }
}

有了Subject(目標)後,須要特殊定製一個含有狀態的目標供觀察者「觀察」this

ConcreteSubject:持有特定的狀態subjectState,並繼承Subject基類,擁有綁定觀察者、刪除觀察者、通知全部觀察者的能力。code

package com.dotleo.observer;

/**
 * 具體的目標對象,負責把有關狀態存入到相應的觀察者對象中
 *
 * @author LiuFei
 * @create 2017-12-03 19:55
 */
public class ConcreteSubject extends Subject {

    // 目標對象的狀態
    private String subjectState;

    public String getSubjectState() {
        return subjectState;
    }

    public void setSubjectState(String subjectState) {
        this.subjectState = subjectState;
        this.notifyObServers();
    }
}

觀察者接口:提供了經過改變的目標去更新本身的狀態server

package com.dotleo.observer;

/**
 * 這是一個觀察者接口,定義一個更新的接口給那些在目標發生變化的時候被通知的對象
 *
 * @author LiuFei
 * @create 2017-12-03 19:58
 */
public interface Observer {
    
    void update(Subject subject);
}

觀察者實例:實現了接口,擁有特定的狀態,實現能夠和目標同步的方法對象

package com.dotleo.observer;

/**
 * 具體的觀察者對象,實現更新的方法,使自身的狀態和目標的狀態保持一致
 *
 * @author LiuFei
 * @create 2017-12-03 19:59
 */
public class ConcreteObserver implements Observer {

    // 觀察者的狀態
    private String observerState;

    /**
     * 獲取目標類的狀態同步到觀察類的狀態中
     * @param subject
     */
    @Override
    public void update(Subject subject) {
        observerState = ((ConcreteSubject) subject).getSubjectState();
    }
}

舉個栗子

黃明在氣象站工做,他能第一時間獲得天氣預報,爲了給女友和老媽貼心的提示,他打算寫一個程序(他會寫代碼),在天天本身改變天氣狀態後,他的女友和老媽能收到提示:繼承

  • 女友收到:明每天氣晴朗,藍天白雲,氣溫28度,是咱們的第一次約會,地點街心公園,不見不散!
  • 老媽收到:明每天氣晴朗,藍天白雲,氣溫28度,是購物的好日子,明天去商城掃物!

因而他想到了觀察者模式。接口

Subject基類:WeatherSubject

package com.dotleo.observer.weather;

import java.util.ArrayList;
import java.util.List;

/**
 * 目標對象,它知道觀察它的觀察者,並提供註冊(添加)和刪除觀察者的接口
 *
 * @author LiuFei
 * @create 2017-12-03 19:53
 */
public class WeatherSubject {

    // 用來保存註冊的觀察者對象
    private List<Observer> observers = new ArrayList<Observer>();

    // attach detach notifyObservers

    /**
     * 添加訂閱天氣的人
     * @param observer
     */
    public void attach(Observer observer) {
        observers.add(observer);
    }

    /**
     * 刪除訂閱天氣的人
     * @param observer
     */
    public void detach(Observer observer) {
        observers.remove(observer);
    }

    /**
     * 通知訂閱天氣的人
     */
    protected void notifyObServers() {
        for (Observer observer : observers) {
            observer.update(this);
        }
    }
}

帶有特定狀態(天氣)的Subject:ConcreteWeatherSubject

package com.dotleo.observer.weather;

/**
 * 具體的目標對象,負責把有關狀態存入到相應的觀察者對象中
 *
 * @author LiuFei
 * @create 2017-12-03 19:55
 */
public class ConcreteWeatherSubject extends WeatherSubject {

    // 目標對象的狀態
    private String weatherContent;

    public String getWeatherContent() {
        return weatherContent;
    }

    public void setWeatherContent(String weatherContent) {
        this.weatherContent = weatherContent;
        this.notifyObServers();
    }


}

Observer接口:Observer

package com.dotleo.observer.weather;

/**
 * 這是一個觀察者接口,定義一個更新的接口給那些在目標發生變化的時候被通知的對象
 *
 * @author LiuFei
 * @create 2017-12-03 19:58
 */
public interface Observer {


    void update(WeatherSubject subject);
}

帶有特定狀態(天氣),信息(觀察者姓名,提醒內容):ConcreteObserver

package com.dotleo.observer.weather;

/**
 * 具體的觀察者對象,實現更新的方法,使自身的狀態和目標的狀態保持一致
 *
 * @author LiuFei
 * @create 2017-12-03 19:59
 */
public class ConcreteObserver implements Observer {

    // 觀察者姓名,誰收到這個消息,黃明的女友或者老媽
    private String observerName;
    // 天氣狀態,從目標處獲取
    private String weatherContext;
    // 提醒內容,黃明的女友提醒約會,而他老媽則提醒購物
    private String remindThing;

    /**
     * 獲取目標類的狀態同步到觀察類的狀態中
     * @param subject
     */
    @Override
    public void update(WeatherSubject subject) {
        weatherContext = ((ConcreteWeatherSubject) subject).getWeatherContent();
        System.out.println(observerName + "收到了" + weatherContext + "," + remindThing);
    }

    public String getObserverName() {
        return observerName;
    }

    public void setObserverName(String observerName) {
        this.observerName = observerName;
    }

    public String getWeatherContext() {
        return weatherContext;
    }

    public void setWeatherContext(String weatherContext) {
        this.weatherContext = weatherContext;
    }

    public String getRemindThing() {
        return remindThing;
    }

    public void setRemindThing(String remindThing) {
        this.remindThing = remindThing;
    }
}

測試類:ObserverTest

package com.dotleo.observer.weather;

/**
 * @author LiuFei
 * @create 2017-12-03 20:33
 */
public class ObserverTest {

    public static void main(String[] args) {
        // 1. 建立一個目標
        ConcreteWeatherSubject weatherSubject = new ConcreteWeatherSubject();
        // 2. 建立觀察者
        ConcreteObserver concreteObserver1 = new ConcreteObserver();
        concreteObserver1.setObserverName("黃明的女友");
        concreteObserver1.setRemindThing("是咱們的第一次約會,地點街心公園,不見不散!");

        ConcreteObserver concreteObserver2 = new ConcreteObserver();
        concreteObserver2.setObserverName("黃明的老媽");
        concreteObserver2.setRemindThing("是購物的好日子,明天去商城掃物!");
        // 3. 註冊觀察者
        weatherSubject.attach(concreteObserver1);
        weatherSubject.attach(concreteObserver2);
        // 4. 目標發佈天氣
        weatherSubject.setWeatherContent("明每天氣晴朗,藍天白雲,氣溫28度");
    }
}

運行結果

輸入圖片說明

優缺點

優勢:

  1. 觀察者和被觀察者是抽象耦合的。
  2. 創建一套觸發機制。

缺點:

  1. 若是一個被觀察者對象有不少的直接和間接的觀察者的話,將全部的觀察者都通知到會花費不少時間。
  2. 若是在觀察者和觀察目標之間有循環依賴的話,觀察目標會觸發它們之間進行循環調用,可能致使系統崩潰。
  3. 觀察者模式沒有相應的機制讓觀察者知道所觀察的目標對象是怎麼發生變化的,而僅僅只是知道觀察目標發生了變化。

應用場景

  1. 對一個對象狀態的更新,須要其餘對象同步更新,並且其餘對象的數量動態可變。
  2. 對象僅須要將本身的更新通知給其餘對象而不須要知道其餘對象的細節。

注意事項

  1. JAVA 中已經有了對觀察者模式的支持類。
  2. 避免循環引用。
  3. 若是順序執行,某一觀察者錯誤會致使系統卡殼,通常採用異步方式。

java內置的觀察者模式

在java.util包中包含有基本的Observer接口和Observable抽象類.功能上和Subject接口和Observer接口相似.不過在使用上,就方便多了,由於許多功能好比說註冊,刪除,通知觀察者的那些功能已經內置好了。

Observer -- > Observer接口
Observable --> Subject基類

**ConcreteWeatherSubject **

package com.dotleo.observer.util;

import java.util.Observable;

public class ConcreteWeatherSubject extends Observable {

    private String weatherContent;

    public String getWeatherContent() {
        return weatherContent;
    }

    public void setWeatherContent(String weatherContent) {
        this.weatherContent = weatherContent;
        this.setChanged();
        this.notifyObservers();
    }
}

**ConcreteObserver **

package com.dotleo.observer.util;

import java.util.Observable;
import java.util.Observer;

/**
 * @author LiuFei
 * @create 2017-12-04 22:37
 */
public class ConcreteObserver implements Observer {

    // 觀察者姓名,誰收到這個消息,黃明的女友或者老媽
    private String observerName;
    // 天氣狀態,從目標處獲取
    private String weatherContext;
    // 提醒內容,黃明的女友提醒約會,而他老媽則提醒購物
    private String remindThing;

    public ConcreteObserver(String observerName, String weatherContext, String remindThing) {
        this.observerName = observerName;
        this.weatherContext = weatherContext;
        this.remindThing = remindThing;
    }

    @Override
    public void update(Observable o, Object arg) {
            weatherContext  = ((ConcreteWeatherSubject ) o).getWeatherContent();
            System.out.println(observerName + "收到了" + weatherContext + "," + remindThing);
    }
}

**ObserverTest **

package com.dotleo.observer.util;

import com.dotleo.observer.weather.ConcreteObserver;

/**
 * @author LiuFei
 * @create 2017-12-04 22:43
 */
public class ObserverTest {

    public static void main(String[] args) {

        // 1. 建立一個目標
        com.dotleo.observer.weather.ConcreteWeatherSubject weatherSubject = new com.dotleo.observer.weather.ConcreteWeatherSubject();
        // 2. 建立觀察者
        com.dotleo.observer.weather.ConcreteObserver concreteObserver1 = new com.dotleo.observer.weather.ConcreteObserver();
        concreteObserver1.setObserverName("黃明的女友");
        concreteObserver1.setRemindThing("是咱們的第一次約會,地點街心公園,不見不散!");

        com.dotleo.observer.weather.ConcreteObserver concreteObserver2 = new ConcreteObserver();
        concreteObserver2.setObserverName("黃明的老媽");
        concreteObserver2.setRemindThing("是購物的好日子,明天去商城掃物!");
        // 3. 註冊觀察者
        weatherSubject.attach(concreteObserver1);
        weatherSubject.attach(concreteObserver2);
        // 4. 目標發佈天氣
        weatherSubject.setWeatherContent("明每天氣晴朗,藍天白雲,氣溫28度");
    }
}
相關文章
相關標籤/搜索