最近在研读了腾讯AlloyTeam前端团队,高级工程师曾探编写的《JavaScript设计模式与开发实践》,所有设计模式的实现都遵循一条原则,即“找出程序中变化的地方,并将变化封装起来”。一个程序的设计总是可以分为可变的部分和不变的部分。当我们找出可变的部分,并且把这部分封装起来,那么剩下的就是不变和稳定的部分。
JavaScript没有提供传统面向对象语言中的类式继承,而是通过原型委托的方式来实现对象与对象之间的继承。JavaScript也没有在语言层面提供对抽象类和接口的支持。所以在JavaScript用设计模式编写代码的时候,要跟传统面向对象语言加以区分。
单例模式的定义是:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
单例模式是一种常用的模式,有一些对象我们往往只需要一个,比如线程池、全军缓存、浏览器中的window对象等。
<html>
<head>
<title>惰性单例-点击登录弹出登录浮窗title>
head>
<body>
<button id="loginBtn">登录button>
<script>
// 创建实例对象的职责
let createLoginLayer = function () {
let div = document.createElement('div')
div.innerHTML = '我是登录浮窗'
div.style.display = 'none'
document.body.appendChild(div)
return div
}
let createIframeLayer = function () {
let iframe = document.createElement('iframe')
document.body.appendChild(iframe)
return iframe
}
// 管理单例的职责
let getSingle = function (fn) {
let result
return function () {
return result || (result = fn.apply(this, arguments))
}
}
// 创建div浮窗
let createSingleLoginLayer = getSingle(createLoginLayer)
// 点击多次都只会创建一个新的登录浮层div
document.getElementById('loginBtn').onclick = function () {
let loginLayer = createSingleLoginLayer()
loginLayer.style.display = 'block';
}
script>
body>
html>
策略模式的定义是:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。
策略模式的目的就是将算法的使用和算法的实现分离开来。说的更详细点就是:定义一些列的算法,把它们各自封装成策略类,算法被封装在策略类内部的方法里。在客户对Context发起请求的时候,Context总是把请求委托给这些策略对象中间的某一个进行计算。
// 普通实现
let calculateBonusCommon = (performanceLevel, salary) => {
if (performanceLevel === 'S') {
return salary * 4
}
if (performanceLevel === 'A') {
return salary * 3
}
if (performanceLevel === 'B') {
return salary * 2
}
}
console.log(calculateBonusCommon('B', 20000))
console.log(calculateBonusCommon('S', 50000))
// 策略模式实现
// 策略类
let strategies = {
'S': (salary) => salary * 4,
'A': (salary) => salary * 3,
'C': (salary) => salary * 2
}
// 环境类
let calculateBonus = (level, salary) => strategies[level](salary)
console.log(calculateBonus('S', 20000))
console.log(calculateBonus('A', 10000))
通过使用策略模式重构代码,我们消除了原程序中大片的条件分支语句。
代理模式是为一个对象提供一个代用品或占位符,以便控制对它的访问。
代理模式的关键是,当客户不方便直接访问一个对象或者不满足需要的时候,提供一个替身对象来控制对这个对象的访问,客户实际上访问的是替身对象。替身对象对请求做出一些处理之后,再把请求转交给本体对象。
<html>
<head>
<title>虚拟代理实现图片预加载title>
head>
<body>
<script>
// 加载图片
let myImage = (function () {
let imgNode = document.createElement('img')
document.body.appendChild(imgNode)
return {
setSrc: function (src) {
imgNode.src = src
}
}
})()
// 代理对象proxyImage
let proxyImage = (function () {
let img = new Image
img.onload = function () {
myImage.setSrc(this.src)
}
return {
setSrc: function (src) {
myImage.setSrc('./loading.gif')
img.src = src
}
}
})()
proxyImage.setSrc('https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1521956229659&di=36a2ea375f48e8328b3cab79e8b1ea0e&imgtype=0&src=http%3A%2F%2Ff0.topitme.com%2F0%2Fa9%2F3e%2F1164210455aae3ea90o.jpg')
script>
body>
html>
如果有一天我们不再需要预加载,那么就不再需要代理对象,可以选择直接请求本体。其中关键是代理对象和本体都对外提供了setSrc方法,在客户看来,代理对象和本体是一致的。
// 乘积
let mult = function () {
console.log('开始计算乘积')
let a = 1
for (let i = 0, l = arguments.length; i < l; i++) {
a *= arguments[i]
}
return a
}
// 加和
let plus = function () {
console.log('开始计算加和')
let a = 1
for (let i = 0, l = arguments.length; i < l; i++) {
a += arguments[i]
}
return a
}
// 减法
let subtraction = function () {
console.log('开始计算减法')
let a = 1
for (let i = 0, l = arguments.length; i < l; i++) {
a -= arguments[i]
}
return a
}
// 缓存代理函数
let proxyMult = (function () {
let cache = []
return function () {
let args = Array.prototype.join.call(arguments, ',')
if (args in cache) {
return cache[args]
}
return cache [args] = mult.apply(this, arguments)
}
})()
console.log(proxyMult(1, 2, 3, 4))
console.log(proxyMult(1, 2, 3, 4))
console.log(proxyMult(1, 2, 3, 4, 5))
// 高阶函数动态创建缓存代理的工厂
let createProxyFactory = function (fn) {
let cache = []
return function () {
let args = Array.prototype.join.call(arguments, ',')
for (args in cache) {
return cache[args]
}
return cache[args] = fn.apply(this, arguments)
}
}
let proxyPlus = createProxyFactory(plus)
let proxySubtrsction = createProxyFactory(subtraction)
console.log(proxyPlus(1, 2, 3, 4))
console.log(proxyPlus(1, 2, 3, 4))
console.log(proxySubtrsction(10, 3, 4))
console.log(proxySubtrsction(10, 3, 4))
迭代器模式是指提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。迭代器模式可以把迭代的过程从业务逻辑中分离出来,在使用迭代器模式之后,即使不关心对象的内部构造,也可以按顺序访问其中的每个元素。
let each = function (ary, callback) {
for (let i = 0, l = ary.length; i < l; i++) {
callback.call(ary[i], i, ary[i])
}
}
each([1, 2, 3, 4], function(i, n) {
console.log([i, n])
})
发布—订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。在JavaScript开发中,我们一般用事件模型来代替传统的发布-订阅模式。
下面看实现发布-订阅模式的步骤:
// 全局发布-订阅对象
let Event = (function () {
// 缓存列表,存放订阅者的回调函数
let clientList = {}
let listen, trigger, remove
// 增加订阅者
listen = (key, fn) => {
if (!clientList[key]) {
clientList[key] = []
}
clientList[key].push(fn)
},
// 发布消息
trigger = (...value) => {
let key = Array.prototype.shift.call(value)
let fns = clientList[key]
// 如果没有绑定的对应的消息
if (!fns || fns.length === 0) {
return false
}
for ( let i = 0, fn; fn = fns[i++]; ) {
fn.apply(this, value)
}
},
// 取消订阅事件
remove = (key, fn) => {
let fns = clientList[key]
// 如果key对应的消息没有被人订阅,则直接返回
if (!fns) {
return false
}
if (!fn) { // 如果没有传入具体的回调函数,表示需要取消key对应消息的所有订阅
fns && (fns.length = 0)
} else {
for (let l = fns.length - 1; l >= 0; l--) {
let _fn = fns[l]
if (_fn === fn) {
fns.splice(l, 1) // 删除订阅者的回调函数
}
}
}
}
return {
listen,
trigger,
remove
}
})()
// 小明订阅消息
Event.listen('squareMeter88', fn1 = function (price, squareMeter) {
console.log('小明先生:')
console.log('price = ' + price)
console.log('squareMeter = ' + squareMeter)
})
// 小红订阅消息
Event.listen('squareMeter88', fn2 = function (price, squareMeter) {
console.log('小红小姐:')
console.log('price = ' + price)
console.log('squareMeter = ' + squareMeter)
})
// 售楼处发布消息
Event.trigger('squareMeter88', 10000, 88)
Event.remove('squareMeter88', fn1)
Event.trigger('squareMeter88', 15000, 88)
命令模式中的命令(command)指的是一个执行某些特定事情的指令。
命令模式最常见的应用场景是:有时候需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是什么。此时希望用一种松耦合的方式来设计程序,使得请求发送者和请求接收者能够消除彼此之间的耦合关系。
let button1 = document.getElementById('button1')
let button2 = document.getElementById('button2')
let button3 = document.getElementById('button3')
let MenuBar = {
refresh: function() {
console.log('刷新菜单界面')
}
}
let SubMenu = {
add: function () {
console.log('增加子菜单')
},
del: function () {
console.log('删除子菜单')
}
}
// 接收者被封闭在闭包产生的环境中,执行命令的操作可以更加简单,仅仅执行回调函数即可
let RefreshMenuBarCommand = function (receiver) {
return {
execute: function () {
receiver.refresh()
}
}
}
let AddSubMenuBarCommand = function (receiver) {
return {
execute: function () {
receiver.add()
}
}
}
let DelSubMenuBarCommand = function (receiver) {
return {
execute: function () {
receiver.del()
}
}
}
// setCommand函数负责往按钮上面安装命令
let setCommand = function (button, command) {
button.onclick = function () {
command.execute()
}
}
let refreshMenuBarCommand = RefreshMenuBarCommand(MenuBar)
setCommand(button1, refreshMenuBarCommand)
let addSubMenuBarCommand = AddSubMenuBarCommand(SubMenu)
setCommand(button2, addSubMenuBarCommand)
let delSubMenuBarCommand = DelSubMenuBarCommand(SubMenu)
setCommand(button2, delSubMenuBarCommand)
宏命令是一组命令的集合,通过执行宏命令的方式,可以一次执行一批命令。
let closeDoorCommand = {
execute: function () {
console.log('关门')
}
}
let openPcCommand = {
execute: function () {
console.log('开电脑')
}
}
let openQQCommand = {
execute: function () {
console.log('登录QQ')
}
}
let MacroCommand = function () {
return {
commandsList: [],
add: function (command) {
this.commandsList.push(command)
},
execute: function() {
for (let i =0, command; command = this.commandsList[i++];) {
command.execute()
}
}
}
}
let macroCommand = MacroCommand()
macroCommand.add(closeDoorCommand)
macroCommand.add(openPcCommand)
macroCommand.add(openQQCommand)
macroCommand.execute()
组合模式就是用小的子对象来构建更大的对象,而这些小的子对象本身也许是由更小的“孙对象”构成的。组合模式将对象组合成树形结构,以表示“部分-整体”的层次结构。除了用来表示树形结构之外,组合模式的另一个好处是通过对象的多态性表现,使得用户对单个对象和组合对象的使用具有一致性。
// 组合模式-扫描文件夹
// 文件夹
let Folder = function (name) {
this.name = name
this.files = []
}
Folder.prototype.add = function (file) {
this.files.push(file)
}
Folder.prototype.scan = function () {
console.log('开始扫描文件夹:' + this.name)
for(let i = 0, file; file = this.files[i++];) {
file.scan()
}
}
// 文件
let File = function (name) {
this.name = name
}
File.prototype.add = function () {
throw new Error('文件下面不能再添加文件')
}
File.prototype.scan = function () {
console.log('开始扫描文件:' + this.name)
}
// 创建文件夹
let folder = new Folder('学习资料')
let folder1 = new Folder('Javascript')
let folder2 = new Folder('JQuery')
// 创建文件
let file1 = new File('Javascript 设计模式与开发实践')
let file2 = new File('精通JQuery')
let file3 = new File('重构与模式')
folder1.add(file2)
folder2.add(file3)
folder.add(file1)
folder.add(file2)
folder.add(file3)
folder.add(folder1)
folder.add(folder2)
folder.scan()
模板方法模式是一种只需使用继承就可以实现的非常简单的模式。模板方法模式由两部分结构组成,第一部分是抽象父类,第二部分是具体的实现子类。在模板方法模式中,子类实现中的相同部分被上移到父类中,而将不同的部分留待子类来实现。这也很好地体现了泛化的思想。
let Beverage = function () {}
Beverage.prototype.boilWater = function () {
console.log('把水煮沸')
}
Beverage.prototype.brew = function () {
throw new Error('子类必须重写brew方法')
}
Beverage.prototype.pourInCup = function () {
throw new Error('子类必须重写pourInCup方法')
}
Beverage.prototype.addCondiments = function () {
throw new Error('子类必须重写addCondiments方法')
}
// 钩子方法
Beverage.prototype.customerWantsCondiments = function () {
return true // 默认需要调料
}
// 模板方法
Beverage.prototype.init = function () {
this.boilWater()
this.brew()
this.pourInCup()
// 如果挂钩返回true。则需要调料
if (this.customerWantsCondiments()) {
this.addCondiments()
}
}
// 泡茶
let Tea = function () {}
Tea.prototype = new Beverage()
Tea.prototype.brew = function () {
console.log('用沸水浸泡茶叶')
}
Tea.prototype.pourInCup = function () {
console.log('把茶倒进杯子')
}
Tea.prototype.addCondiments = function () {
console.log('加柠檬')
}
Tea.prototype.customerWantsCondiments = function () {
return window.confirm('请问需要调料吗?')
}
let tea = new Tea()
tea.init()
//泡咖啡
let Coffee = function () {}
Coffee.prototype = new Beverage()
Coffee.prototype.brew = function () {
console.log('用沸水冲泡咖啡')
}
Coffee.prototype.pourInCup = function () {
console.log('把咖啡倒进杯子')
}
Coffee.prototype.addCondiments = function () {
console.log('加牛奶和糖')
}
let coffee = new Coffee()
coffee.init()
利用好莱坞原则,下面的代码可以达到和继承一样的效果
let Beverage = function (param) {
let boilWater = function () {
console.log('把水煮沸')
}
let brew = param.brew || function () {
throw new Error('必须传递brew方法')
}
let pourInCup = param.pourInCup || function () {
throw new Error('必须传递pourInCup方法')
}
let addCondiments = param.addCondiments || function () {
throw new Error('必须传递addCondiments方法')
}
let customerWantsCondiments = param.customerWantsCondiments ? true : false
let F = function () {}
// 模板方法
F.prototype.init = function () {
boilWater()
brew()
pourInCup()
if( customerWantsCondiments) {
addCondiments()
}
}
return F
}
let Coffee = Beverage({
brew: function () {
console.log('用沸水冲泡咖啡')
},
pourInCup: function () {
console.log('把咖啡倒入杯子')
},
addCondiments: function () {
console.log('加糖和牛奶')
}
})
let coffee = new Coffee()
coffee.init()
let Tea = Beverage ({
brew: function () {
console.log('用沸水泡茶')
},
pourInCup: function () {
console.log('把茶倒入杯子')
},
customerWantsCondiments: false
})
let tea = new Tea()
tea.init()
享元模式是一种用于性能优化的模式。享元模式的核心是运用共享技术来有效支持大量细粒度的对象。
享元模式要求将对象的属性划分为内部状态和外部状态,把所有内部状态相同的对象都指定为同一个共享的对象。而外部状态可以从对象身上剥离出来,并存储在外部。
<html>
<head>
<title>享元模式文件上传title>
head>
<body>
<script>
// uploadType是内部状态,fileName和fileSize是根据场景而变化,每个文件fileName和
// fileSize都不一样,fileName和fileSize没有办法被共享,它们只能被划分为外部状态
let Upload = function (uploadType) {
this.uploadType = uploadType
}
Upload.prototype.delFile = function (id) {
uploadManager.setExternalState(id, this)
if (this.fileSize < 3000) {
return this.dom.parentNode.removeChild(this.dom)
}
if (window.confirm('确定要删除该文件吗?' + this.fileName)) {
return this.dom.parentNode.removeChild(this.dom)
}
}
// 工厂进行对象实例化,如果某种内部状态对应的共享对象已经被创建过,那么直接返回
// 这个对象,否则创建一个新的对象
let UploadFactory = (function () {
let createFlyWeightObjs = {}
return {
create: function (uploadType) {
if (createFlyWeightObjs[uploadType]) {
return createFlyWeightObjs[uploadType]
}
return createFlyWeightObjs[uploadType] = new Upload(uploadType)
}
}
})()
// 管理器封装外部状态
let uploadManager = (function () {
// 保存所有upload对象的外部状态
let uploadDatabase = {}
return {
add: function (id, uploadType, fileName, fileSize) {
let flyWeightObj = UploadFactory.create(uploadType)
let dom = document.createElement('div')
dom.innerHTML = '文件名称:' + fileName + ',文件大小:' + fileSize + '' +
''
dom.querySelector('.delFile').onclick = function () {
flyWeightObj.delFile(id)
}
document.body.appendChild(dom)
uploadDatabase[id] = {
fileName: fileName,
fileSize: fileSize,
dom: dom
}
return flyWeightObj
},
setExternalState: function (id, flyWeightObj) {
let uploadData = uploadDatabase[id]
for (let i in uploadData) {
flyWeightObj[i] = uploadData[i]
}
}
}
})()
// 触发上传动作
let id = 0
window.startUpload = function (uploadType, files) {
for (let i = 0, file; file = files[i++];) {
let uploadObj = uploadManager.add(++id, uploadType, file.fileName, file.fileSize)
}
}
startUpload('plugin', [
{
fileName: '1.txt',
fileSize: 1000
},
{
fileName: '2.html',
fileSize: 3000
},
{
fileName: '3.txt',
fileSize: 5000
}
])
startUpload('flash', [
{
fileName: '4.txt',
fileSize: 1000
},
{
fileName: '5.html',
fileSize: 3000
},
{
fileName: '6.txt',
fileSize: 5000
}
])
script>
body>
html>
职责链模式的定义是:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系,将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
let order500 = function (orderType, pay, stock) {
if (orderType === 1 && pay) {
console.log('500元定金预购,得到100优惠券')
} else {
return 'nextSuccessor'
}
}
let order200 = function (orderType, pay, stock) {
if(orderType === 2 && pay) {
console.log('200元定金预购,得到50优惠券')
} else {
return 'nextSuccessor'
}
}
let orderNomal = function (orderType, pay, stock) {
if (stock > 0) {
console.log('普通购买,无优惠券')
} else {
console.log('手机库存不足')
}
}
// 用AOP实现职责链
Function.prototype.after = function (fn) {
let self = this
return function () {
let ret = self.apply(this, arguments)
if (ret === 'nextSuccessor') {
return fn.apply(this, arguments)
}
return ret
}
}
let order = order500.after(order200).after(orderNomal)
order(1, true, 500)
order(2, true, 500)
order(3, true, 500)
order(1, false, 0)
中介者模式的作用就是解除对象与对象之间的紧耦合关系。增加一个中介者对象后,所有的相关对象都通过中介者对象来通信,而不是互相引用,所以当一个对象发生改变时,只需要通知中介者对象即可。中介者使各个对象之间耦合松散,而且可以独立地改变它们之间的交互。
/**
* @description player对象的原型方法中,不负责具体的执行逻辑,而是把操作转交给中介者对象。
* @param {any} name
* @param {any} teamColor
*/
function Player (name, teamColor) {
this.state = 'live' // 玩家状态
this.name = name // 角色名字
this.teamColor = teamColor // 队伍颜色
}
// 玩家胜利
Player.prototype.win = function () {
console.log('winner: ' + this.name)
}
// 玩家失败
Player.prototype.lose = function () {
console.log('loser: ' + this.name)
}
// 玩家死亡
Player.prototype.die = function () {
this.state = 'dead'
// 给中介者发送消息,玩家死亡
playerDirector.ReceiveMessage('playerDead', this)
}
// 移除玩家
Player.prototype.remove = function () {
// 给中介者发送消息,移除一个玩家
playerDirector.ReceiveMessage('removePlayer', this)
}
// 玩家换队
Player.prototype.changeTeam = function (color) {
// 给中介者发送消息,玩家换队
playerDirector.ReceiveMessage('changeTeam', this, color)
}
/**
* @description 工厂函数
*/
let playerFactory = function (name, teamColor) {
// 创造一个新的玩家对象
let newPlayer = new Player(name, teamColor)
// 给中介者发送消息,新增玩家
playerDirector.ReceiveMessage('addPlayer', newPlayer)
return newPlayer
}
/**
* @description 中介者
*/
let playerDirector = (function () {
// 保存所有玩家
let players = {}
// 中介者可以执行的操作
let operations = {}
/******************* 新增一个玩家 ******************/
operations.addPlayer = function (player) {
// 玩家队伍的颜色
let teamColor = player.teamColor
// 如果该颜色的玩家还没有成立队伍,则新成立一个队伍
players[teamColor] = players[teamColor] || []
// 添加玩家进队伍
players[teamColor].push(player)
}
/******************* 移除一个玩家 ******************/
operations.removePlayer = function (player) {
// 玩家的队伍颜色
let teamColor = player.teamColor
// 该队伍的所有成员
let teamPlayers = players[teamColor] || []
// 遍历删除
for (let i = teamPlayers.length - 1; i >=0; i--) {
if(teamPlayers[i] === player) {
teamPlayers.splice(i, 1)
}
}
}
/******************* 玩家换队 ******************/
operations.changeTeam = function (player, newTeamColor) {
operations.removePlayer(player)
player.teamColor = newTeamColor
operations.addPlayer(player)
}
/******************* 玩家死亡 ******************/
operations.playerDead = function (player) {
// 玩家的队伍颜色
let teamColor = player.teamColor
// 玩家所在队伍
let teamPlayers = players[teamColor]
let all_dead = true
for (let i = 0, player; player = teamPlayers[i++];) {
if (player.state !== 'dead') {
all_dead = false
break
}
}
if (all_dead) {
// 本队所有玩家都输了
for(let i = 0, player; player = teamPlayers[i++];) {
player.lose()
}
for (let color in players) {
if (color !== teamColor) {
// 其他队伍的玩家
let teamPlayers = players[color]
// 其他队伍所有玩家胜利
for (let i = 0, player; player = teamPlayers[i++];) {
player.win()
}
}
}
}
}
/********* 负责接收player对象发送的消息 *********/
let ReceiveMessage = function () {
let message = Array.prototype.shift.call(arguments)
operations[message].apply(this, arguments)
}
return {
ReceiveMessage: ReceiveMessage
}
})()
// 红队
let player1 = playerFactory('皮蛋', 'red')
let player2 = playerFactory('小乖', 'red')
let player3 = playerFactory('小强', 'red')
let player4 = playerFactory('小雪', 'red')
let player5 = playerFactory('小明', 'red')
// 蓝队
let player6 = playerFactory('黑妞', 'blue')
let player7 = playerFactory('兔头', 'blue')
let player8 = playerFactory('胖墩', 'blue')
let player9 = playerFactory('海盗', 'blue')
let player10 = playerFactory('大仙', 'blue')
// 黄队
let player11 = playerFactory('大牛', 'yellow')
let player12 = playerFactory('小王', 'yellow')
let player13 = playerFactory('小刘', 'yellow')
let player14 = playerFactory('小陈', 'yellow')
let player15 = playerFactory('小马', 'yellow')
player1.remove()
player2.changeTeam('yellow')
player3.die()
player4.die()
player5.die()
装饰者模式能够在不改变对象自身的基础上,在程序运行期间给对象动态地添加职责。
Function.prototype.before = function (beforefn) {
// 保存原函数的引用
let _self = this
// 返回包含了原函数好新函数的代理函数
return function () {
// 执行函数,且保证this不会被劫持,新函数接受的参数也会
// 原封不动地传入原函数,新函数在原函数之前执行
beforefn.apply(this, arguments)
// 执行原函数并返回原函数的执行结果,并且保证this不被劫持
return _self.apply(this, arguments)
}
}
Function.prototype.after = function (afterfn) {
let _self = this
return function () {
let ret = _self.apply(this, arguments)
afterfn.apply(this, arguments)
return ret
}
}
window.onload = function () {
console.log('1')
}
window.onload = (window.onload() || function () {}).after(function () {
console.log('2')
}).after(function () {
console.log('3')
}).after(function () {
console.log('4')
})
状态模式的定义:允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。
状态模式的关键是区分事物内部的状态,事物内部状态的改变往往会带来事物的行为改变。状态模式把事物的每种状态都封装成单独的类,跟此种状态有关的行为都被封装在这个类的内部。
<html>
<body>
<script>
// 每个状态都必须实现buttonWasPressed方法
let State = function () { }
State.prototype.buttonWasPressed = function () {
throw new Error('父类的buttonWasPressed方法必须被重写')
}
// OffLightState
let OffLightState = function (light) {
this.light = light
}
OffLightState.prototype = new State()
OffLightState.prototype.buttonWasPressed = function () {
console.log('弱光')
// 切换状态到weakLightState
this.light.setState(this.light.weakLightState)
}
// WeakLightState
let WeakLightState = function (light) {
this.light = light
}
WeakLightState.prototype = new State()
WeakLightState.prototype.buttonWasPressed = function () {
console.log('强光')
this.light.setState(this.light.strongLightState)
}
// StrongLightState
let StrongLightState = function (light) {
this.light = light
}
StrongLightState.prototype = new State()
StrongLightState.prototype.buttonWasPressed = function () {
console.log('关灯')
this.light.setState(this.light.offLightState)
}
// Light类
let Light = function () {
this.offLightState = new OffLightState(this)
this.weakLightState = new WeakLightState(this)
this.strongLightState = new StrongLightState(this)
this.button = null
}
Light.prototype.init = function () {
let button = document.createElement('button')
let self = this
this.button = document.body.appendChild(button)
this.button.innerHTML = '开关'
this.currState = this.offLightState
this.button.onclick = function () {
self.currState.buttonWasPressed()
}
}
Light.prototype.setState = function (newState) {
this.currState = newState
}
let light = new Light()
light.init()
script>
body>
html>
适配器模式主要用来解决两个已有接口之间不匹配的问题。使用适配器模式之后,原本由于接口不兼容而不能工作的两个软件实体可以一起工作。
let googleMap = {
show: function () {
console.log('开始渲染谷歌地图')
}
}
let baiduMap = {
show: function () {
console.log('开始渲染谷歌地图')
}
}
let sosoMap = {
display: function () {
console.log('开始渲染搜搜地图')
}
}
// 适配器模式
let sosoMapAdapter = {
show: function () {
return sosoMap.display()
}
}
let renderMap = function (map) {
if (map.show instanceof Function) {
map.show()
}
}
renderMap(googleMap)
renderMap(baiduMap)
renderMap(sosoMapAdapter)