系列目錄: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 請求實際上是複雜的,咱們能夠經過封裝來簡化
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! 趙信 */
複製代碼
但這只是一局比賽的狀況,假若咱們此時存在掉線或者更換隊伍的狀況,那麼上面的這種形式是晚覺沒法解決的,因此此時咱們須要一箇中介者來統管全部的玩家。
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! 趙信 */
複製代碼
中介者模式就是用來下降耦合度的,全部若是你的代碼或者模塊中耦合度較高,依賴過分,對實際調用和維護產生了影響,那麼就能夠經過中介者模式來下降耦合度。