JavaScript 設計模式解析【4】—— 裝飾者模式,外觀模式,中介者模式

系列目錄:javascript

裝飾者模式

裝飾者模式是在不改變對象自身的基礎上,在程序運行期間給對象動態地添加方法。java

本章節的示例須要 babel 支持修飾器模式ajax

裝飾者模式很是貼合 JavaScript 動態語言的特性,由於咱們能夠輕易的改變某個對象,但同時,由於函數是一等公民,因此咱們會避免直接改寫某個函數,來保護代碼的可維護性和可擴展性。typescript

其實就像咱們拍照後添加的濾鏡,不一樣的濾鏡給照片賦予了不一樣的意境,這就是裝飾者模式,經過濾鏡裝飾了照片,而並無修改照片自己就給其添加了功能。編程

下面來舉一個例子:設計模式

初始實例

加入此時你是覺得勇者 但勇者當前只有初始數值服務器

class Warrior {
    constructor(atk=50, def=50, hp=100, mp=100) {
        this.init(atk,def,hp,mp)
    }

    init(atk, def, hp, mp) {
        this.atk = atk
        this.def = def
        this.hp = hp
        this.mp = mp
    }

    toString() {
        return `攻擊力:${this.atk}, 防護力: ${this.def}, 血量: ${this.hp}, 法力值: ${this.mp}`
    }
}


const Reaper = new Warrior()

console.log(`勇者狀態 => ${Reaper}`) // 勇者狀態 => 攻擊力:50, 防護力: 50, 血量: 100, 法力值: 100
複製代碼

裝飾器的使用

以後咱們爲勇者配備一把聖劍,建立一個 decorateSword 方法,注意這個方法是裝飾在init 上的babel

// 本質是使用了 Object.defineProperty 方法
function decorateSword(target, key, descriptor) {
    // 首先獲取到 init 方法
    const initMethod = descriptor.value
    // 寶劍添加攻擊力 100 點
    let moreAtk = 100
    let returnObj
    descriptor.value = (...args) => {
        args[0] += moreAtk
        returnObj = initMethod.apply(target, args)
        return returnObj
    }
}



class Warrior {
    constructor(atk=50, def=50, hp=100, mp=100) {
        this.init(atk,def,hp,mp)
    }
    @decorateSword
    init(atk, def, hp, mp) {
        this.atk = atk
        this.def = def
        this.hp = hp
        this.mp = mp
    }

    toString() {
        return `攻擊力:${this.atk}, 防護力: ${this.def}, 血量: ${this.hp}, 法力值: ${this.mp}`
    }
}


const Reaper = new Warrior()

console.log(`勇者狀態 => ${Reaper}`) // 勇者狀態 => 攻擊力:150, 防護力: 50, 血量: 100, 法力值: 100
複製代碼

裝飾器的疊加

如今,咱們這位勇者的防護力還過低了,咱們須要爲勇者再增添一個護甲app

// 省略decorateSword

function decorateArmour(target, key, descriptor) {
    // 首先獲取到 init 方法
    const initMethod = descriptor.value
    // 護甲添加防護力 100 點
    let moreDef = 100
    let returnObj
    descriptor.value = (...args) => {
        args[1] += moreDef
        returnObj = initMethod.apply(target, args)
        return returnObj
    }
}


class Warrior {
    constructor(atk=50, def=50, hp=100, mp=100) {
        this.init(atk,def,hp,mp)
    }
    @decorateSword
    @decorateArmour
    init(atk, def, hp, mp) {
        this.atk = atk
        this.def = def
        this.hp = hp
        this.mp = mp
    }

    toString() {
        return `攻擊力:${this.atk}, 防護力: ${this.def}, 血量: ${this.hp}, 法力值: ${this.mp}`
    }
}


const Reaper = new Warrior()

console.log(`勇者狀態 => ${Reaper}`) // 勇者狀態 => 攻擊力:150, 防護力: 150, 血量: 100, 法力值: 100
複製代碼

咱們成功的讓勇者升級了,他終於能夠戰勝大魔王了(沒準仍是個史萊姆)函數

總結:

裝飾者通常也用來實現 AOP (面向切面編程)利用AOP能夠對業務邏輯的各個部分進行隔離,也能夠隔離業務無關的功能好比日誌上報、異常處理等,從而使得業務邏輯各部分之間的耦合度下降,提升業務無關的功能的複用性,也就提升了開發的效率。

裝飾者模式與代理模式相似,但代理模式的意圖是不直接訪問實體,爲實體提供一個替代者,實體內定義了關鍵功能,而代理提供或拒絕對實體的訪問,或者一些其餘額外的事情。而裝飾者模式的做用就是爲對象動態地加入行爲。

外觀模式

外觀模式爲一組複雜的子系統接口提供一個更高級的統一接口,經過這個接口使得對子系統接口的訪問更容易,不符合單一職責原則和開放封閉原則。

其實外觀模式很常見,它其實就是經過一個單獨的函數,來簡化對一個或多個更大型,更爲複雜的函數的訪問,是一種對複雜操做的封裝。

封裝Ajax

初始化一個原生 Ajax 請求實際上是複雜的,咱們能夠經過封裝來簡化

function ajaxCall(type, url, callback, data) {
    let xhr = (function(){
        try {
            // 標準方法
            return new XMLHttpRequest()
        }catch(e){}

        try {
            return new ActiveXObject("Msxm12.XMLHTTP")
        }catch(e){}
    }())
    STATE_LOADED = 4
    STATUS_OK = 200

    // 一但從服務器收到表示成功的相應消息,則執行所給定的回調方法
    xhr.onreadystatechange = function () {
        if (xhr.readyState !== STATE_LOADED) {
            return
        }
        if (xhr.state == STATUS_OK) {
            callback(xhr.responseText)
        }
    }

    // 發出請求
    xhr.open(type.toUpperCase(), url)
    xhr.send(data)
}
複製代碼

封裝以後,咱們發送請求的樣子就變成了

// 使用封裝的方法
ajaxCall("get", "/url/data", function(res) {
    document.write(res)
})
複製代碼

總結

​ 外觀模式適用於當須要同時有多個複雜操做時,經過把複雜操做封裝,調用時直接用方法調用,提升代碼的可閱讀性和可維護性。

中介者模式

中介者模式的主要做用是解除對象之間的強耦合關係,經過增長一箇中介者,讓全部的對象經過中介者通訊,而不是相互引用,因此當一個對象發生改變時,只須要通知中介者對象便可。

中介者但是使網狀的多對多關係變爲相對簡單的一對多關係。

情景示例

假設此時有兩隊人在玩 英雄聯盟 ,必須團滅對方全部玩家才能得到勝利。下面將分爲藍紅方:

class Player {
    constructor(name, teamColor) {
        this.name = name // 英雄名稱
        this.teamColor = teamColor // 隊伍顏色
        this.teammates = [] // 隊友列表
        this.enemies = [] // 敵人列表
        this.state = 'alive' // 存活狀態
    }
    // 獲勝
    win() {
        console.log(`Vicotry! ${this.name}`)
    }
    // 失敗
    lose() {
        console.log(`Defeat! ${this.name}`)
    }
    // 死亡方法
    die() {
        // 團滅標誌
        let ace_flag = true
        // 設置玩家狀態爲死亡
        this.state = 'dead'
        // 遍歷隊友列表,若沒有團滅,則還未失敗
        for(let i in this.teammates) {
            if (this.teammates[i].state !== 'dead') {
                ace_flag = false
                break
            }
        }
        // 若是已被團滅
        if (ace_flag === true) {
            // 己方失敗
            this.lose()
            for(let i in this.teammates) {
                this.teammates[i].lose()
            }
            // 敵方勝利
            for(let i in this.enemies) {
                this.enemies[i].win()
            }
        }
    }
}
// 玩家列表
const Players = []

// 定義一個工廠函數來生成玩家

function playerFactory (name, teamColor) {
    let newPlayer = new Player(name, teamColor)
    // 通知全部玩家 新角色加入
    for(let i in Players) {
        if (Players[i].teamColor === teamColor) {
            Players[i].teammates.push(newPlayer)
            newPlayer.teammates.push(Players[i])
        } else {
            Players[i].enemies.push(newPlayer)
            newPlayer.enemies.push(Players[i])
        }
    }
    Players.push(newPlayer)
    return newPlayer
}

// 開始比賽
// 藍色方
let hero1 = playerFactory('蓋倫', 'Blue')
let hero2 = playerFactory('皇子', 'Blue')
let hero3 = playerFactory('拉克絲', 'Blue')
let hero4 = playerFactory('劍姬', 'Blue')
let hero5 = playerFactory('趙信', 'Blue')

// 紅色方
let hero6 = playerFactory('諾手', 'Red')
let hero7 = playerFactory('德萊文', 'Red')
let hero8 = playerFactory('卡特琳娜', 'Red')
let hero9 = playerFactory('烏鴉', 'Red')
let hero10 = playerFactory('賽恩', 'Red')


// 紅色方被團滅
hero6.die()
hero7.die()
hero8.die()
hero9.die()
hero10.die()


/* 運行結果: Defeat! 賽恩 Defeat! 諾手 Defeat! 德萊文 Defeat! 卡特琳娜 Defeat! 烏鴉 Vicotry! 蓋倫 Vicotry! 皇子 Vicotry! 拉克絲 Vicotry! 劍姬 Vicotry! 趙信 */
複製代碼

但這只是一局比賽的狀況,假若咱們此時存在掉線或者更換隊伍的狀況,那麼上面的這種形式是晚覺沒法解決的,因此此時咱們須要一箇中介者來統管全部的玩家。

使用中介者模式重構

  • Player 和 palyerFactory 基礎操做不變
  • 將操做轉角給中介者對象
const GameManager = ( function() {
    // 存儲全部玩家
    const players = []
    // 操做實體
    const operations = {}
    // 新增玩家
    operations.addPlayer = function (player) {
        let teamColor = player.teamColor
        players[teamColor] = players[teamColor] || []; // 若是該顏色的玩家尚未成立隊伍,則新成立一個隊伍 
        players[teamColor].push(player); // 添加玩家進隊伍
    }
    // 玩家掉線
    operations.playerDisconnect = function (player) {
        // 玩家隊伍顏色
        let teamColor = player.teamColor
        let teamPlayer = players[teamColor]
        for(let i in teamPlayer) {
            if (teamPlayer[i].name = player.name) {
                teamPlayer.splice(i, 1)
            }
        }
    }

    // 玩家死亡
    operations.playerDead = function (player) {
        let teamColor = player.teamColor
        teamPlayers = players[teamColor]
        // 團滅標誌
        let ace_flag = true
        // 設置玩家狀態爲死亡
        this.state = 'dead'
        // 遍歷隊友列表,若沒有團滅,則還未失敗
        for(let i in teamPlayers) {
            if (teamPlayers[i].state !== 'dead') {
                ace_flag = false
                break
            }
        }
        // 若是已被團滅
        if (ace_flag === true) {
            // 己方失敗
            for(let i in teamPlayers) {
                teamPlayers[i].lose()
            }
            // 敵方勝利
            for(let color in players) {
                if (color !== teamColor) {
                    let teamPlayers = players[color]
                    teamPlayers.map(player => {
                        player.win()
                    })
                }
            }
        }
    }

    function reciveMessage (message, player) {
        operations[message](player)
    }

    return {
        reciveMessage: reciveMessage
    }
})()




class Player {
    constructor(name, teamColor) {
        this.name = name // 英雄名稱
        this.teamColor = teamColor // 隊伍顏色
        this.state = 'alive' // 存活狀態
    }
    // 獲勝
    win() {
        console.log(`Vicotry! ${this.name}`)
    }
    // 失敗
    lose() {
        console.log(`Defeat! ${this.name}`)
    }
    // 死亡方法
    die() {
        // 設置玩家狀態爲死亡
        this.state = 'dead'
        // 向中介者發送死亡的宣告
        GameManager.reciveMessage('playerDead', this)
    }
    // 玩家掉線
    disconnect() {
        GameManager.reciveMessage('playerDisconnect', this)
    }
}
// 玩家列表
const Players = []

// 定義一個工廠函數來生成玩家

function playerFactory (name, teamColor) {
    let newPlayer = new Player(name, teamColor)
    // 通知中介者新增玩家
    GameManager.reciveMessage('addPlayer', newPlayer)
    return newPlayer
}

// 開始比賽
// 藍色方
let hero1 = playerFactory('蓋倫', 'Blue')
let hero2 = playerFactory('皇子', 'Blue')
let hero3 = playerFactory('拉克絲', 'Blue')
let hero4 = playerFactory('劍姬', 'Blue')
let hero5 = playerFactory('趙信', 'Blue')

// 紅色方
let hero6 = playerFactory('諾手', 'Red')
let hero7 = playerFactory('德萊文', 'Red')
let hero8 = playerFactory('卡特琳娜', 'Red')
let hero9 = playerFactory('烏鴉', 'Red')
let hero10 = playerFactory('賽恩', 'Red')


// 紅色方被團滅
hero6.die()
hero7.die()
hero8.die()
hero9.die()
hero10.die()

/* 運行結果: Defeat! 賽恩 Defeat! 諾手 Defeat! 德萊文 Defeat! 卡特琳娜 Defeat! 烏鴉 Vicotry! 蓋倫 Vicotry! 皇子 Vicotry! 拉克絲 Vicotry! 劍姬 Vicotry! 趙信 */



複製代碼

什麼時候使用?

中介者模式就是用來下降耦合度的,全部若是你的代碼或者模塊中耦合度較高,依賴過分,對實際調用和維護產生了影響,那麼就能夠經過中介者模式來下降耦合度。

總結

  • 中介者模式符合最少知識原則
  • 中介者模式下降了對象和模塊之間的耦合度
  • 中介者模式使複雜的網狀多對多模型轉換爲相對簡單的一對多關係。
  • 中介者模式也存在必定的缺點,例如中介者對象相對複雜且龐大,有時每每中介者自己難以維護。
相關文章
相關標籤/搜索