學習這些設計模式,讓你寫出更優雅的代碼

寫代碼容易,寫出優雅的代碼難,寫易於維護的、容易擴展的、結構清晰的代碼應該是每位開發者努力的目標,而學習設計模式,合理的的使用能讓咱們離這個目標更進一步。最近看了《Javascript設計模式與開發實踐》這本書,一言以蔽之,真不錯的一本書,在這裏總結一下書中介紹的主要的在JavaScript中咱們能夠用到的一些設計模式。設計模式的思想是值得反覆咀嚼、思考的,在之後的業務實現中,應該結合這些思想,加以合理的使用git

更多內容angularjs

單例模式

單例模式保證類只有一個實例,並提供一個訪問它的全局訪問點

js中實現github

function getSingle(fn){
    let result

    return function (){
        return result || (result=fn.apply(this,arguments))
    }

  }

策略模式

解決一個問題的多個方法,將每種方法獨立封裝起來,相互能夠替換

一個基於策略模式的程序至少由兩部分組成,一個是一組策略類,策略類封裝了具體的算法,並負責具體的計算過程,一個是環境類,環境類接受客戶的請求,隨後把請求委託給某個策略類算法

策略模式的一個使用場景:表單驗證,將不一樣驗證規則封裝成一組策略,避免了多重條件判斷語句redux

一句經典的話:在函數做爲一等對象的語言中,策略模式是隱性的,策略就是值爲函數的變量設計模式

例子:app

const S = (salary)=>{
        return salary * 4
    }
    const A = (salary)=>{
        return salary * 3
    }
    const B = (salary)=>{
        return salary * 2
    }

    const calculate = (fun,salary)=>{
        return fun(salary)
    }
    calculate(S,1000)

代理模式

代理模式爲一個對象提供一個代用品或佔位符,以便控制對它的訪問

不直接和本體進行交互,而是在中間加入一層代理,代理來處理一些不須要本體作的操做框架

var myImage=function(){
    var imgNode=document.createElement('img')
    document.body.appendChild(imgNode)
    return {
        setImg(src){
            imgNode.src=src
        }
    }
}

var proxyImg=function(){
    var img =new Image()
    img.onload=function(){
        myImage.setSrc(this.src)
    }
    return {
        setImg(src){
            myImage.setSrc(‘loading.png’)
            img.src=src      
        }
    }
}

代理的意義dom

對單一職責原則的一種表現,單一職責原則指的是,一個函數或類應該只負責一件事,如何一個函數職責太多,等於把這些職責耦合在了一塊兒,當一部分須要改動時,頗有可能就影響到了函數的其餘部分函數

觀察者和發佈訂閱模式

觀察者和發佈、訂閱模式使程序的兩部分沒必要緊密耦合在一塊兒,而是經過通知的方式來通訊

  • 觀察者模式
一個對象維持一系列依賴於它的對象,當對象狀態發生改變時主動通知這些依賴對象

這裏注意是對象直接管理着依賴列表,這點也是觀察者模式和發佈、訂閱模式的主要區別

class Subject{
        constructor(){
            this.observers=[]
        }
        add(observer){
            this.observers.push(observer)
        }
        notify(data){
            for(let observer of this.observers){
                observer.update(data)
            }
        }
    }

    class Observer{
        update(){

        }
    }

觀察者模式的缺點是對象必須本身維護一個觀察者列表,當對象狀態有更新時,直接調用其餘對象的方法,因此,在使用中,咱們通常採用一種變形方式,即發佈訂閱模式

  • 發佈訂閱模式

該模式在主題和觀察者之間加入一層管道,使得主題和觀察者不直接交互,發佈者將內容發佈到管道,訂閱者訂閱管道里的內容,目的是避免訂閱者和發佈者之間產生依賴關係

class Pubsub{

        constuctor(){
            this.pubsub={}
            this.subId=-1
        }

        publish(topic,data){
            if(!this.pubsub[topic]) return
            const subs=this.pubsub[topic]
            const len=subs.length
            while(len--){
                subs[len].update(topic,data)
            }
        }

        /**
        *  topic {string}
        *  update {function}
        */
        subscribe(topic,update){
            !this.pubsub[topic] && (this.pubsub[topic]=[])
            this.subId++
            this.pubsub[topic].push({
                token:this.subId,
                update
            })
        }

        unsubscribe(token){
            for(let topic in this.pubsub){
                if(this.pubsub.hasOwnProperty(topic)){
                    const current=this.pubsub[topic]
                    for(let i=0,j=current.length;i<j;i++){
                        if(current[i].token==token){
                            current.splice(i,1)
                            return token
                        }
                    }
                }
            }
            return this
        }

    }

發佈訂閱模式是在框架設計中常常使用的一種設計模式,angularjs中的自定義事件,Rxjs,狀態管理的redux等都能看到它的身影

命令模式

命令模式的命令指的是一個執行某些特定事情的指令

命令模式最多見的使用場景是:有時候須要向某些對象發送請求,可是不知道請求的接受者是誰,也不知道被請求的操做是什麼。此時但願用一種鬆耦合的方式來設計程序,是使得請求發送者和接受者消除彼此之間的耦合關係

命令模式的由來,實際上是回調函數的一個面向對象的替代品

一句話來講,命令模式就是用一個函數來包裹一個具體的實現,這個函數統必定義了一個execute方法來調用具體的實現方法,而請求者只要和這個命令函數交流就行

享元模式

享元模式顧名思義,共享一些單元,用於優化重複、緩慢及數據共享效率較低的代碼

應用:一是用於數據層,處理內存中保存的大量類似對象的共享數據,二是用於DOM層,事件代理

在享元模式中,有個有關兩個狀態的概念-內部和外部

內部狀態存儲於對象內部,能夠被一些對象共享,獨立於具體的場景,一般不會變

外部狀態根據場景而變化

剝離了外部狀態的對象成爲共享對象,外部狀態在必要時被傳入共享對象來組成一個完整的對象

使用享元模式的幾個步驟:

以書中文件上傳的例子描述

  1. 剝離外部狀態
class Upload{

    constructor(type){
        this.uploadType=type
    }

    delFile(id){
        uploadManager.setExternalState(id,this) //這裏就是組裝外部狀態來使共享對象變成一個具體的對象
        if(this.fileSize<3000){
            //直接刪除
            return
        }
        //彈窗詢問確認刪除?
    }

 }
  1. 使用工廠進行對象實例化
var UploadFactory=(function(){
        const flyWeightObjs={}
        return {
            create(uploadType){
                if(flyWeightObjs[uploadType]){
                    return flyWeightObjs[uploadType]
                }
                return flyWeightObjs[uploadType]=new Upload(uoloadType)
            }
        }
    })()
  1. 使用管理器封裝外部狀態
var uploadManager=(function(){
    var uploadDatabase={}
    return {
        add(id,uploadType,fileSize,fileName){
            var flyWeightObj=UploadFactory.create(uploadType) //那個被共享的對象
            //建立結點...
            //刪除操做
            dom.onclick=function(){
                flyWeightObj.delFile(id) //這個共享在步驟1中會被組合,能夠看到,只有在刪除操做的時候,咱們才須要那些外部狀態
            }
            uploadDatabase[id]={
                fileName,
                fileSize,
                dom
            }
            return flyWeightObj
        },
        setExternalState(id,flyWeight){
            var externalState=uploadDatabase[id]
            Object.assign(flyWeight,externalState)
        }
    }
})()

責任鏈模式

將一個請求以此傳遞給多個函數,若請求符合當前函數要求,則當前函數處理,不然,傳給下一個

很好很強大

責任鏈模式能夠很好的避免大量的if,else if,else

if (Function.prototype.chainAfter) {
    throw new Error('the chainAfter method already exist')
} else {
    Function.prototype.chainAfter = function (fn) {
        return (...args) => {
            const ret = this.apply(this, [...args, () => {
                return fn && fn.apply(this, args)
            }])
            if (ret === 'NEXT') {
                return fn && fn.apply(this, args)
            }
            return ret
        }
    }
}

/**
 * example
 * class Test{
 *  
 *     test(...args){
 *            alert('test')
 *            return 'NEXT'
 *     }
 * 
 *     test1(...args){
 * 
 *            setTimeout(()=>{
 *                  alert('test1')
 *                  args.pop()()
 *            })   
 *     }
 * 
 *     test2(...args){
 *            alert('test2')
 *     }
 * 
 *      $onInit(){
 *          const chain = this.test.bind(this)
 *                .chainAfter(this.test1.bind(this))
 *                .chainAfter(this.test2.bind(this))
 *         chain(1,2,3)
 *      }
 * }
 * 
 */

裝飾者模式

在不改變原有函數或對象功能的基礎上,給它們新加功能

用AOP裝飾函數

if (Function.prototype.before) {
    throw new Error('the before method already exist')
} else {
    Function.prototype.before = function (beforefn) {
        return () => {
            if (beforefn.apply(this, arguments)) {
                this.apply(this, arguments)
            }
        }
    }
}

if (Function.prototype.after) {
    throw new Error('the after method already exist')
} else {
    Function.prototype.after = function (afterfn) {
        return () => {
            this.apply(this, arguments)
            afterfn.apply(this, arguments)
        }
    }
}

狀態模式

容許一個對象在其內部狀態改變時改變它的行爲,對象看起來彷佛修改了它的類

要點:將狀態封裝成獨立的函數,並將請求委託給當前的狀態對象,當對象的內部狀態改變時,會帶來不一樣的行爲變化

電燈的例子:

一個按鈕控制電燈的開關,按一下是開,再按一下是關

初始實現:

class Light{
    constructor(){
        this.state='off',
        this.button=null
    }
    init(){
        //建立按鈕結點
        .....

        this.button.onClick=()=>{
            this.btnPressed()
        }
    }
    btnPressed(){
        if(this.state=='off'){
            this.state='on
        }else {
            this.state='off'
        }
    }
}

這段代碼的缺點就是不易擴展,當要加入一種閃動的狀態時,就要修改btnPressed中的代碼

使用狀態模式改寫

class Light{
    constructor(){
        this.state=FSM.off,
        this.button=null
    }
    init(){
        //建立按鈕結點
        .....

        this.button.onClick=()=>{
            this.state.btnPressed.call(this)
        }
    }
}

const FSM={
    on:{
        btnPressed(){
            //處理
            this.state=FMS.on
        }
    },
    off:{
        btnPressed(){
            //處理
            this.state=FMS.off
        }
    }
}
相關文章
相關標籤/搜索