天天看點

JavaScript設計模式-( 觀察者模式 與 釋出、訂閱模式 )總結

JavaScript設計模式整理

一些常見模式實作:點我

什麼是設計模式?

一個模式就是一個可重用的方案,可應用于在軟體設計中的常見問題。另一種解釋就是一個我們如何解決問題的模闆 - 那些可以在許多不同的情況裡使用的模闆。

設計模式的分類:

建立型設計模式: 1、簡單工廠模式 2、工廠方法模式 3、抽象工廠模式 4、建造者模式 5、原型模式 6、單例模式

結構型設計模式: 7、外觀模式 8、擴充卡模式 9、代理模式 10、裝飾者模式 11、橋接模式 12、組合模式 13、享元模式

行為型設計模式: 14、模闆方法模式 15、觀察者模式 16、狀态模式 17、政策模式 18、職責鍊模式 19、指令模式 20、通路者模式 21、中介者模式 22、備忘錄模式 23、疊代器模式 24、解釋器模式

技巧型設計模式: 25、鍊模式 26、委托模式 27、資料通路對象模式 28、節流模式 29、簡單模闆模式 30、惰性模式 31、參與者模式 32、等待者模式

架構型設計模式: 33、同步子產品模式 34、異步子產品模式 35、Widget模式 36、MVC模式 37、MVP模式 38、MVVM模式

備注:該分類借鑒于《JavaScript設計模式-張容銘》

觀察者模式

所謂觀察者模式,其實就是為了實作松耦合(loosely coupled)。

觀察者模式指的是一個對象(Subject)維持一系列依賴于它的對象(Observer),當有關狀态發生變更時 Subject 對象則通知一系列 Observer 對象進行更新。

在觀察者模式中,Subject 對象擁有添加、删除和通知一系列 Observer 的方法等等,而 Observer 對象擁有更新方法等等。

在 Subject 對象添加了一系列 Observer 對象之後,Subject 對象則維持着這一系列 Observer 對象,當有關狀态發生變更時 Subject 對象則會通知這一系列 Observer 對象進行更新。

用《Head First設計模式》裡的氣象站為例子,每當氣象測量資料有更新,

changed()

方法就會被調用,于是我們可以在

changed()

方法裡面,更新氣象儀器上的資料,比如溫度、氣壓等等。

但是這樣寫有個問題,就是如果以後我們想在

changed()

方法被調用時,更新更多的資訊,比如說濕度,那就要去修改

changed()

方法的代碼,這就是緊耦合的壞處。

怎麼解決呢?使用觀察者模式,面向接口程式設計,實作松耦合。

觀察者模式裡面,

changed()

方法所在的執行個體對象,就是被觀察者(Subject,或者叫Observable),它隻需維護一套觀察者(Observer)的集合,這些Observer實作相同的接口,Subject隻需要知道,通知Observer時,需要調用哪個統一方法就好了:
JavaScript設計模式-( 觀察者模式 與 釋出、訂閱模式 )總結

(原文位址:https://juejin.im/post/6844903733738864654 )

function Subject(){
  this.observers = [];
}

Subject.prototype = {
  add:function(observer){  // 添加
    this.observers.push(observer);
  },
  remove:function(observer){  // 删除
    var observers = this.observers;
    for(var i = 0;i < observers.length;i++){
      if(observers[i] === observer){
        observers.splice(i,1);
      }
    }
  },
  notify:function(){  // 通知
    var observers = this.observers;
    for(var i = 0;i < observers.length;i++){
      observers[i].update();
    }
  }
}

function Observer(name){
  this.name = name;
}

Observer.prototype = {
  update:function(){  // 更新
    console.log('my name is '+this.name);
  }
}

var sub = new Subject();

var obs1 = new Observer('ttsy1');
var obs2 = new Observer('ttsy2');

sub.add(obs1);
sub.add(obs2);
sub.notify();  //my name is ttsy1、my name is ttsy2
           

上述代碼中,我們建立了 Subject 對象和兩個 Observer 對象,當有關狀态發生變更時則通過 Subject 對象的 notify 方法通知這兩個 Observer 對象,這兩個 Observer 對象通過 update 方法進行更新。

在 Subject 對象添加了一系列 Observer 對象之後,還可以通過 remove 方法移除某個 Observer 對象對它的依賴。

var sub = new Subject();

var obs1 = new Observer('ttsy1');
var obs2 = new Observer('ttsy2');

sub.add(obs1);
sub.add(obs2);
sub.remove(obs2);
sub.notify();  //my name is ttsy1
           

釋出訂閱模式

釋出訂閱模式指的是希望接收通知的對象(Subscriber)基于一個主題通過自定義事件訂閱主題,被激活事件的對象(Publisher)通過釋出主題事件的方式通知各個訂閱該主題的 Subscriber 對象。
function EventTarget(){
            //EventTart類型有一個單獨的屬性handlers,用于存儲事件處理程式
            this.handlers = {};
        }

        EventTarget.prototype = {
            //重新指向EventTarget函數
            constructor: EventTarget,
            //type:事件類型 handler: 用于處理該事件的函數
            addHandler:function(type,handler){
                //判斷handlers對象中是否存在屬性名為type的屬性
                if(typeof this.handlers[type] == "undefined"){
                    //如果不存在就将屬性名為type屬性的屬性值設定為一個新數組,用于存放type類型的事件處理程式
                    this.handlers[type] = [];
                }
                //将handler事件處理函數添加到數組末尾
                this.handlers[type].push(handler);
            },

            //fire()函數要接受一個至少包含type屬性的對象作為參數
            fire: function(event){
                if(!event.target){
                    event.target = this;
                }
                //判斷要觸發的消息是否存在
                if(this.handlers[event.type] instanceof Array){
                    //擷取對應類型的事件處理程式
                    var handlers = this.handlers[event.type];
                    for(var i=0,len = handlers.length; i<len; i++){
                        //調用每個函數,并給出event對象
                        handlers[i](event);
                    }
                }
            },

            removeHandler: function(type, handler){
                //防止删除的消息不存在的情況
                if(this.handlers[type] instanceof Array){
                    var handlers = this.handlers[type];
                    for(var i=0,len = handlers.length;i<len;i++){
                        if(handlers[i] === handler){
                            break;
                        }
                    }
                    //将i位置的事件處理程式删除
                    handlers.splice(i,1);
                }
            }
        };
           

EventTarget類型有一個單獨的屬性handlers,用于儲存事件處理程式。還有三個方法:                                                        

1.  addHandler(),用于注冊給定類型事件的事件處理程式;  

2.  fire(),用于觸發一個事件;

3.  removeHandler(),用于登出某個事件類型的事件處理程式。

addHandler()方法接受兩個參數: 事件類型和用于處理該事件的函數。當調用該方法時,會進行一次檢查,看看handlers屬性中是否已經存在一個針對該事件類型的數組;如果沒有,則建立一個新的。然後使用push()将該處理程式添加到數組的末尾。

如果要觸發一個事件,要調用fire()函數。該方法接受一個單獨的參數,是一個至少包含type屬性的對象。fire()方法給 event對象設定一個 target屬性,如果它尚未被指定的話。然後它就查找對應該事件類型的一組處理程式,調用各個函數,并給出event對象。因為這些都是自定義事件,是以 event 對象上還需要的額外資訊由你自己決定.

removeHandler()方法是 addhandler()的輔助,它們接受的參數一樣:事件的類型和事件處理程式。這個方法搜尋事件處理程式的數組找到要删除的處理程式的位置。如果找到了,則使用 break操作符退出for循環。然後使用splice方法将該項目從數組中删除.

    然後使用EventTarget類型的自定義事件可以如下使用:

function handleMessage(event){
        alert("message received:" + event.message);
    }
    //建立一個新對象
    var target = new EventTarget();
    //添加一個事件處理程式
    target.addHandler("message",handleMessage);
    //觸發事件
    target.fire({type:"message",message:"Hello world!"});
    //删除事件處理程式
    target.removeHandler("message",handleMessage);
    //再次觸發,應該沒有處理程式
    target.fire({type:"message",message:"Hello world!"});
           

在這段代碼中,定義了 handleMessage()函數用于處理 message事件。它接受 event對象并輸出 message屬性。調用 target對象的 addHandler()方法并傳給" message以及 handleMessage()函數。在接下來的一行上,調用了fire()函數,并傳遞了包含2個屬性,即type和 message的對象直接量。它會調用 message事件的事件處理程式,這樣就會顯示一個警告框(來自 handleMessage())

然後删除了事件處理程式,這樣即使事件再次觸發,也不會顯示任何警告框。

    因為這種功能是封裝在一種自定義類型中的,其他對象可以繼承 EventTarget并獲得這個行為,如下所示:

function Person(name , age){
        EventTarget.call(this);
        this.name = name;
        this.age = age;
    }


    //組合繼承方法
    // function inheritPrototype(subType,superType){
    //     var prototype = object(superType.prototype);
    //     prototype.constructor = subType;
    //     subType.prototype = prototype;
    // }

    inheritPrototype(Person,EventTargetart);

    Person.prototype.say = function(message){
        this.fire({type:"message",message:'message'});
    };
           
Person類型使用了寄生組合繼承方法來繼承 EventTarget。一旦調用了say()方法,便觸發了事件,它包含了消息的細節。在某種類型的另外的方法中調用fire()方法是很常見的,同時它通常不是公開調用的。這段代碼可以照如下方式使用:
function handleMessage (event)(
alert (event. target name says
event message)i
//建立新 person
var person: new Person("Nicholas" 29)
//添加一個事件處理程式
person addHandler("message", handleMessage)
//在該對象上調用1個方法,它觸發消息事件
person say("Hi there.");
           

這個例子中的handleMessage()函數顯示了某人名字(通過 event.target.name獲得)的一個警告框和消息正文。當調用say()方法并傳遞一個消息時,就會觸發 message事件。接下來,它又會調用 handleMessage()函數并顯示警告框。

當代碼中存在多個部分在特定時刻互相互動的情況下,自定義事件就非常有用了。這時,如果每全對象都有對其他所有對象的引用,那麼整個代碼就會緊密耦合,同時維護也變得很困難,因為對某個對象的修改也會影響到其他對象,使用自定義事件有助于解耦相關對象,保持功能的隔絕。在很多情況中觸發事件的代碼和監聽事件的代碼是完全分離的。

在釋出訂閱模式裡,釋出者,并不會直接通知訂閱者,換句話說,釋出者和訂閱者,彼此互不相識。

互不相識?那他們之間如何交流?

答案是,通過第三者,也就是在消息隊列裡面,我們常說的經紀人Broker。

JavaScript設計模式-( 觀察者模式 與 釋出、訂閱模式 )總結

釋出者隻需告訴Broker,我要發的消息,topic是AAA;

訂閱者隻需告訴Broker,我要訂閱topic是AAA的消息;

于是,當Broker收到釋出者發過來消息,并且topic是AAA時,就會把消息推送給訂閱了topic是AAA的訂閱者。當然也有可能是訂閱者自己過來拉取,看具體實作。

也就是說,釋出訂閱模式裡,釋出者和訂閱者,不是松耦合,而是完全解耦的。

放一張極簡的圖,給大家對比一下這兩個模式的差別:

JavaScript設計模式-( 觀察者模式 與 釋出、訂閱模式 )總結

總結

從表面上看:
  • 觀察者模式裡,隻有兩個角色 —— 觀察者 + 被觀察者
  • 而釋出訂閱模式裡,卻不僅僅隻有釋出者和訂閱者兩個角色,還有一個經常被我們忽略的 —— 經紀人Broker
往更深層次講:
  • 觀察者和被觀察者,是松耦合的關系
  • 釋出者和訂閱者,則完全不存在耦合
從使用層面上講:
  • 觀察者模式,多用于單個應用内部
  • 釋出訂閱模式,則更多的是一種跨應用的模式(cross-application pattern),比如我們常用的消息中間件

此部分參考:https://juejin.im/post/6844903733738864654 

繼續閱讀