1. 程式人生 > >設計模式九: 觀察者模式(Observer Pattern)

設計模式九: 觀察者模式(Observer Pattern)

簡介

觀察者屬於行為型模式的一種, 又叫釋出-訂閱模式. 如果一個物件的狀態發生改變,依賴他的物件都將發生變化, 那麼這種情況就適合使用觀察者模式.

它包含兩個術語,主題(Subject),觀察者(Observer), 主題管理一個觀察者的列表, 並在狀態發生變化時通知到他們.

實現層面上, 主題定義了一個觀察者列表並可以管理這個列表(將觀察者註冊進去和撤銷註冊的行為), 同時定義了通知到所有觀察者的行為.

Java類庫有預設的觀察者實現類Observer. 使用觀察者模式的還有事件監聽, RxJava等.

意圖

定義物件之間的一對多依賴,當一個物件狀態改變時,它的所有依賴都會收到通知並且自動更新狀態。

類圖

實現

一. 定義抽象觀察者

/**
 * 觀察者抽象角色
 */
public interface MyObserver {
    void update(Subject subject);
}

二. 定義抽象主題

/**
 * 主題抽象角色
 */
public abstract class Subject {
    private ArrayList<MyObserver> list = new ArrayList<MyObserver>();

    public Subject register(MyObserver observer){
        if(!list.contains(observer)){
            list.add(observer);
        }
        return this;
    }

    public Subject unRegister(MyObserver observer){
        if(list.contains(observer)){
            list.remove(observer);
        }
        return this;
    }

    public final void notifyObservers(){
        for (MyObserver item:list) {
            item.update(this);
        }
    }

    /**
     * 具體狀態的變更
     */
    abstract void change();
}

三. 定義具體主題

/**
 * 具體主題角色
 */
@Data
public class ConcreteSubject extends Subject {
    private String name;
    public void change() {
        this.setName("王多魚");
        super.notifyObservers();
    }
}

四. 定義觀察者的實現, 分別定義兩個不同實現

public class UncleObserver implements MyObserver {
    public void update(Subject subject) {
        ConcreteSubject concreteSubject = (ConcreteSubject)subject;
        System.out.println("UncleObserver 知道了: "+concreteSubject.getName());
    }
}

public class HentaiObserver implements MyObserver {
    public void update(Subject subject) {
        ConcreteSubject concreteSubject = (ConcreteSubject)subject;
        System.out.println("HentaiObserver 知道了: "+concreteSubject.getName());
    }
}

五. 呼叫, 首先註冊觀察者,然後呼叫主題的變更方法

// 例項化主題後為其註冊了兩個觀察者的例項, 並最終呼叫主題的變更方法觸發通知行為
public static void main(String[] args) {
    new ConcreteSubject()
            .register(new UncleObserver())
            .register(new HentaiObserver())
            .change();
}

總結

關鍵點: 定義通知到觀察者的方法要注意, 如果有耗時嚴重的會阻塞其他觀察者得到通知, 可以非同步多執行緒實現; 避免迴圈引用;

觀察者分推模式和拉模式, 推模式是指將主題的變更資訊傳送給觀察者, 觀察者可以用, 也可以不用. 拉模式則是將主題的引用傳送給觀察者, 觀察者通過引用根據自己需要獲取所需資訊. 示例中使用的就是這種模式.

程式碼修改為推模式

// 觀察者的方法接收具體資訊
void update(String name);

// 主題的通知方法做相應修改
public final void notifyObservers(){
    for (MyObserver item:list) {
        item.update("王多魚");
    }
}