【設計模式】從前端的角度去理解觀察者模式

!!最下面有完整程式碼

 

設計模式是幹嘛的?

設計模式實際上就是給出某種場景下一個”解題思路”.它不是面向於業務的,而是”實現”層面的.其實對於初步接觸這個概念的人比較懵逼.我們需要在進一步了解之前明確一個點,不同的設計模式是應對不同場景的.就比如”工廠模式”和”觀察者模式”他們是沒法比較的,他們一個是用於”創造”,一個是用於”監聽”的.所以我們去理解某個設計模式的時候一定要先去了解它是用於哪種場景的,然後在這種場景下它有什麼優勢.

 

觀察者模式

核心需求

前面有提到,觀察者模式是為了”監聽”的.也就是說,我需要對多個數據進行監聽.同時這些數據可能需要在某條數據更新後進行一系列操作.

 

需求分析

1.多個數據同時監聽一個共同的數據,當這個數據發生變化時,那些監聽的對象都得知道.

2.收到通知後執行其更新函數.

 

需求案例

假如我現在有以下數據

let test1 = {name:'小明',age:18}
let test2 = {name:'小紅',age:17}
let test3 = {name:'小智',age:16}

 然後,我

let me  ={
    name:'shyno',
    age:18
}

我的需求是:當”me”自我介紹完之後,”小明”、”小紅”、”小智”都要自我介紹一下

首先,這些只是原始數據.如果不考慮設計模式,我直接就寫一些函數去解析對比,然後列印.不過顯然,為了能復用,我需要抽一些函數出來.廣義點講,這些函數就是”設計模式”的一部分,所以沒必要糾結”設計模式”在前端的正確意義.

 

模式分析

首先,我們需要處理每一個觀察對象,對它進行包裝和綁定.他們都有一個固定函數,這個函數用來處理更新之後的邏輯.類似下面的

let getNewData= function(data){
    let newData = {...data}
    newData.whenUpdate=function(){
        console.log(`我的名字是:${this.name},我的年齡是:${this.age}`)
    }
return newData 
}

 可是,有一種更方便的方案,即構造函數.因為你其他的屬性我並不關心,只需要加一個whenUpdate函數就夠了.那我們把這個構造函數叫做”觀察者”,也就是說它一直盯著變化,也只盯著變化

觀察者

//觀察者
function Observer(){
   this.whenUpdate=function(){
    // console.log(`我的名字是:${this.name},我的年齡是:${this.age}`)   
   }
}

 我們可以在whenUpdate裡面寫上公共邏輯,但是實際上假如大家雖然都需要在更新的時候做一件事,可是不是同一件事,比如test1改變後,需要列印name,test2改變後需要列印age,test3是兩個都列印,那很明顯,他們的共同點只有一個,即只有whenUpdate.綜上所述,觀察者只是提供一個名字加whenUpdate的函數.

//觀察者
function Observer(){
   this.whenUpdate=function(){
   
   }
}

 然後,再結合需求去具體化觀察者.

function ObserverSpecific(obj){
    Observer.apply(this)
    this.name =obj.name
    this.age =obj.age
    //重寫whenUpdate
    this.whenUpdate=function(){
        console.log(`我的名字是:${this.name},我的年齡是:${this.age}`) 
    }
}
let Observer_test1 =new ObserverSpecific(test1)
let Observer_test2=new ObserverSpecific(test2)
let Observer_test3=new ObserverSpecific(test3)

 這樣我們就得到了三個具體的觀察者.

 

觀察者列表

當其中一個變化,大家都有反應,這個”大家”很明顯是在一個集合里的.我們通過這個集合去儲存和遍歷他們.所以我們需要一個列表去存這些具體觀察者.同時這個列表本身也應該是可操作的.最基礎的增刪改查應該是要有的.

//抽象觀察者列表
function ObserverSpecificList(){
   this.List = []
   this.add=function(option){
    this.List.push(option)
   }
   this.remove=function(option){
    this.List=this.List.filter((item)=>item!==option)
   }
   this.getOption=function(index){
    this.List=this.List.filter((item,i)=>i===index)[0]
   }
   this.update=function(data){
       
   }
}

 再根據觀察者去具體化觀察者列表,這裡不把update寫死的原因還是觀察者里的那個更新函數不是固定的,我們是whenUpdate,但是可能其他觀察者我們定義的”afterUpdate”等函數名,所以我們先抽象化一個觀察者列表,再去具體化他.

//具體觀察者列表
function ObserverSpecificListSpecific(){
    ObserverSpecificList.apply(this)
    //重寫update
    this.update=function(data){
        this.List.forEach(element => {
            element.whenUpdate(data)
        });
    }
}

 

可觀察對象(被觀察者)

在這個案例中”me”是被觀察者,也就是說大家都等著我呢.那這個”me”有什麼特性呢?

首先,”me”是被觀察的,那麼有哪些觀察者觀察我呢?所以就需要一個觀察者列表,那我同時也能操作這個列表,由此可知.被觀察者實際上是繼承於觀察者列表的.雖然他們看上去很不一樣.

//可觀察對象
function Observed (obj){
    ObserverSpecificListSpecific.apply(this) //被觀察者實際上擁有觀察者列表的所有屬性
    this.name =obj.name      //同時被觀察者有自己特有的屬性和函數
    this.age =obj.age
    //重寫update
    this.say=function(data){
       console.log(`我的名字是:${this.name},我的年齡是:${this.age}.我自我介紹完了,輪到你們自我介紹了;`)
       this.update()
    }
}

 也就是說,作為可觀察對象,他是能通過觀察者列表去讓所有的觀察者的whenUpdate去執行的.那我們從程式碼流程的角度去分析一下,觀察者模式實際上就是,給每個被觀察者加上觀察者列表相關屬性,觀察者列表裡所有的元素都是觀察者,他們都有個函數去執行更新後的操作(whenUpdate),他們把這個操作給了觀察者列表.讓觀察者列表放在一個函數中執行(update).當被觀察者執行某個函數之後(say)就可以調用觀察者列表的這個函數(update),然後所有觀察者的更新後的操作(whenUpdate)也得到了執行.

 

完整程式碼

//觀察者
function Observer(){
   this.whenUpdate=function(){
    // console.log(`我的名字是:${this.name},我的年齡是:${this.age}`)   
   }
}
//抽象觀察者列表
function ObserverSpecificList(){
   this.List = []
   this.add=function(option){
    this.List.push(option)
   }
   this.remove=function(option){
    this.List=this.List.filter((item)=>item!==option)
   }
   this.getOption=function(index){
    this.List=this.List.filter((item,i)=>i===index)[0]
   }
   this.update=function(data){

   }
}
//觀察者示例
function ObserverSpecific(obj){
    Observer.apply(this)
    this.name =obj.name
    this.age =obj.age
    //重寫whenUpdate
    this.whenUpdate=function(data){
        console.log(`我收到了${data}的自我介紹邀請,我的名字是:${this.name},我的年齡是:${this.age}`) 
    }
}
//具體觀察者列表
function ObserverSpecificListSpecific(){
    ObserverSpecificList.apply(this)
    //重寫update
    this.update=function(data){
        this.List.forEach(element => {
            element.whenUpdate(data)
        });
    }
}
//抽象可觀察對象
function Observed (obj){
    ObserverSpecificListSpecific.apply(this) //被觀察者實際上擁有觀察者列表的所有屬性
    this.say=function(){
    }
}
//可觀察對象
function ObservedSpecific (obj){
    Observed.apply(this) 
    this.name =obj.name      //同時被觀察者有自己特有的屬性和函數
    this.age =obj.age
    //重寫say
    this.say=function(){
       console.log(`我的名字是:${this.name},我的年齡是:${this.age}.我自我介紹完了,輪到你們自我介紹了;`)
       this.update(this.name)
    }
}
/*-------------分割線-------------*/
let test1 = {name:'小明',age:18}
let test2 = {name:'小紅',age:17}
let test3 = {name:'小智',age:16}
let me  ={
    name:'shyno',
    age:18
}
let Observed_me =new ObservedSpecific(me)         //將"me"示例化成被觀察對象
let Observer_test1 =new ObserverSpecific(test1)  //將"test1"示例化成觀察者
let Observer_test2=new ObserverSpecific(test2)  //將"test2"示例化成觀察者
let Observer_test3=new ObserverSpecific(test3)  //將"test3"示例化成觀察者
Observed_me.add(Observer_test1)   //"me"將"test1"放到自己的觀察者列表裡
Observed_me.add(Observer_test2)  //"me"將"test2"放到自己的觀察者列表裡
Observed_me.add(Observer_test3) //"me"將"test3"放到自己的觀察者列表裡
// Observed_me.remove(Observer_test1) //"me"將"test1"移出觀察者列表
Observed_me.say()

 執行結果