設計模式(9) 觀察者模式

ajajaz發表於2022-04-24

觀察者模式

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

介紹

意圖:定義物件間的一種一對多的依賴關係,當一個物件的狀態發生改變時,所有依賴於它的物件都得到通知並被自動更新。

主要解決:一個物件狀態改變給其他物件通知的問題,而且要考慮到易用和低耦合,保證高度的協作。

何時使用:一個物件(目標物件)的狀態發生改變,所有的依賴物件(觀察者物件)都將得到通知,進行廣播通知。

如何解決:使用物件導向技術,可以將這種依賴關係弱化。

**關鍵程式碼v:在抽象類裡有一個 ArrayList 存放觀察者們。

應用例項: 1、拍賣的時候,拍賣師觀察最高標價,然後通知給其他競價者競價。 2、西遊記裡面悟空請求菩薩降服紅孩兒,菩薩灑了一地水招來一個老烏龜,這個烏龜就是觀察者,他觀察菩薩灑水這個動作。

優點: 1、觀察者和被觀察者是抽象耦合的。 2、建立一套觸發機制。

缺點: 1、如果一個被觀察者物件有很多的直接和間接的觀察者的話,將所有的觀察者都通知到會花費很多時間。 2、如果在觀察者和觀察目標之間有迴圈依賴的話,觀察目標會觸發它們之間進行迴圈呼叫,可能導致系統崩潰。 3、觀察者模式沒有相應的機制讓觀察者知道所觀察的目標物件是怎麼發生變化的,而僅僅只是知道觀察目標發生了變化。

使用場景

一個抽象模型有兩個方面,其中一個方面依賴於另一個方面。將這些方面封裝在獨立的物件中使它們可以各自獨立地改變和複用。
一個物件的改變將導致其他一個或多個物件也發生改變,而不知道具體有多少物件將發生改變,可以降低物件之間的耦合度。
一個物件必須通知其他物件,而並不知道這些物件是誰。
需要在系統中建立一個觸發鏈,A物件的行為將影響B物件,B物件的行為將影響C物件……,可以使用觀察者模式建立一種鏈式觸發機制。
注意事項: 1、JAVA 中已經有了對觀察者模式的支援類。 2、避免迴圈引用。 3、如果順序執行,某一觀察者錯誤會導致系統卡殼,一般採用非同步方式。

實現

1,明星效應

class Star {
    state: string = ''
    observers: Array<Fan> 
    constructor(public name:string) {
        this.name = name;
        this.observers = [];
    }

    getState() {
        return this.state;
    }

    setState(state:string) {
        this.state = state;
        this.notifyAllobservers();
    }

    attach(observer:Fan) {
        this.observers.push(observer);
    }

    notifyAllobservers() {
        if(this.observers.length>0) {
            this.observers.forEach(observer => {
                observer.update();
            })
        }
    }
}

class Fan {
    constructor(public name:string,public star:Star) {
        this.name = name;
        this.star = star;
        this.star.attach(this);
    }

    update() {
        console.log(`我的明星喜歡${this.star.getState()},我也喜歡`);
    }
}
let star = new Star('Angular Baby');
let f1 = new Fan('張三',star);
star.setState('藍色');

2,jquery Callbacks實現

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    
</body>
<script>
    window.jQuery = {
        Callbacks() {
            let callbacks = [];

            function add(fn) {
                callbacks.push(fn);
            }

            function remove(fn) {
                callbacks = callbacks.filter(item => item!=fn);
            }

            function fire(fn) {
                callbacks.forEach(element => {
                    element();
                });
            }

            return {
                add,
                remove,
                fire
            }
        }
    }
    let callbacks = jQuery.Callbacks();
    console.log(callbacks);
    function a1() {console.log('a1')}
    function a2() {console.log('a2')}
    function a3() {console.log('a3')}
    callbacks.add(a1);
    callbacks.add(a2);
    callbacks.add(a3);
    callbacks.remove(a3);
    callbacks.fire();
</script>
</html>

3,creaeStore實現

function createStore(reducer) {
    let state;
    let listeners = [];
    function getState() {
        return state;
    }
    function subscribe(listener) {
        listeners.push(listener);
    }

    function dispatch(actions) {
        state = reducer(state,action);
        listeners.forEach(listener => listener());
    }

    return {
        getState,
        subscribe,
        dispatch
    }
}

4,eventEmitter實現

class eventEmitter {
    events: Array<Array<Function>> = []
    constructor() {

    }

    on(type: any, listener: Function) {
        let listeners = this.events[type];
        if (listeners) {
            listeners.push(listener);
        } else {
            this.events[type] = [listener];
        }
    }

    emit(type:any,...rest:any) {
        let listeners = this.events[type];
        let args = Array.prototype.slice.call(arguments).slice(1);
        if(listeners) {
            listeners.forEach(fn => {
                fn(...args);
            })
        }
    }
}

let emit = new eventEmitter();
emit.on('click',function(a:any,b:any,c:any) {
    console.log(a,b,c);
})

emit.emit('click',1,2,3);

5,釋出訂閱實現

import { join } from "path";

class Agent {
    _events: any = {}
    constructor() {

    }

    subscribe(type:string,listener:Function) {
        let listeners = this._events[type];
        if(listeners) {
            this._events[type].push(listener);
        } else {
            this._events[type] = [listener];
        }
    }

    publish(type:string,...rest:any) {
        let listeners = this._events[type];
        let args = Array.prototype.slice.call(arguments,1);
        if(listeners) {
            listeners.forEach((fn:Function) => fn(...args));
        }
    }
}

//房東
class LandLoad {
    constructor(public name:string) {
    
    }
    lend(agent:Agent,houseName:string,area:string,money:string) {//向外出租
        agent.publish(houseName,area,money);
    }
}

//租客 
class Tenant {
    constructor(public name:string) {

    }

    rent(agent:Agent,houseName: string) {
        agent.subscribe(houseName,(area:string,money:string)=>{
            console.log(`我看到中介的新房源了 ${area}平方 租金 ${money}元`)
        });
    }
}

let agent = new Agent();
let t1 = new Tenant('張三');
let t2 = new Tenant('李四');
t1.rent(agent,'house');
t2.rent(agent,'home');
let landLoad = new LandLoad('gzq');
landLoad.lend(agent,'home','60','12000');

相關文章