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! 赵信 */



复制代码

什么时候使用?

中介者模式就是用来下降耦合度的,全部若是你的代码或者模块中耦合度较高,依赖过分,对实际调用和维护产生了影响,那么就能够经过中介者模式来下降耦合度。

总结

  • 中介者模式符合最少知识原则
  • 中介者模式下降了对象和模块之间的耦合度
  • 中介者模式使复杂的网状多对多模型转换为相对简单的一对多关系。
  • 中介者模式也存在必定的缺点,例如中介者对象相对复杂且庞大,有时每每中介者自己难以维护。
相关文章
相关标签/搜索