《javascript设计模式与开发实践》读书笔记(6)
本次笔记记录本周学习的模版方法模式和享元模式。
模版方法模式
定义:模版方法模式由两部分组成,第一部分是抽象父类,第二部分是具体实现的子类。通常在抽象父类中封装了子类的算法框架,包括实现一些公共方法以及封装子类中所有方法的执行顺序。子类通过继承这个抽象类,也继承了整个算法结构,并且可以选择重写父类的方法。简单来说,模版方法模式就是通过继承来把多个子类的公共部分进行封装。
一个简单例子
书中用了Coffee和Tea的例子来说明模版方法模式。
泡一杯咖啡需要四步:
- 把水煮沸
- 沸水冲咖啡
- 把咖啡倒进杯子
- 加糖和牛奶
泡一杯茶与泡一杯咖啡的步骤相似: - 把水煮沸
- 沸水浸泡茶叶
- 茶水倒进杯子
- 加柠檬
泡茶和泡咖啡步骤相似,所以可以将公共部分分离出来,写到父类中。如下代码
var Beverage = function(){}
Beverage.prototype.boilWater = function(){
console.log('把水煮沸')
}
Beverage.prototype.brew = function(){}
Beverage.prototype.pourInCup = function(){}
Beverage.prototype.addCondiments = function(){}
Beverage.prototype.init = function(){
this.boilWater()
this.brew()
this.pourInCup()
this.addCondiment()
} 定义完父类,可以在子类中对特定方法进行重写。比如Tea类:
var Tea = function(){}
Tea.prototype = new Beverage()
Tea.prototype.brew = function(){
console.log('沸水浸泡茶叶')
}
Tea.prototype.pourInCup = function(){
console.log('把茶倒进杯子')
}
Tea.prototype.addCondiments = function(){
console.log('加柠檬')
}
var tea = new Tea()
tea.init() 这个例子中,Beverage.prototype.init就是模版方法,其指导了子类以何种顺序执行哪些方法。
抽象类
从上面的例子可以看出,模版方法模式的父类是一个抽象类,其不应该被实例化。在js语言中,并没有对抽象类的支持,编译器不会检查子类是否重写了父类的“抽象方法”。所以在js中实现抽象类,需要在抽象类的抽象方法中抛出异常,以便在程序运行中可以知道哪里出错了。也可以用鸭子类型来模拟接口检查,在创建对象时来发现问题,但这种方法实现起来较复杂(具体实现后续研究一下)。
钩子方法
钩子方法是指,在父类中放置钩子,根据钩子方法的返回值,决定是否执行某些方法,钩子方法的返回由子类决定。这样可以给方法的执行增加灵活性。实现起来也很简单,用if语句即可。
好莱坞原则
好莱坞原则是指底层组件把自己挂钩到高层组件,高层组件决定什么时候,以何种方式来调用底层组件。模版方法模式就是好莱坞原则的一个使用场景。根据好莱坞原则,我们实际并不需要一个“正宗”的通过继承实现的模版方法模式,在js语言中,我们完成可以不通过继承来实现。代码如下:
var Beverage = function(param){
var boilWater = function(){
console.log('把水煮沸')
}
var brew = param.brew || function(){
throw new Error('必须传递brew方法')
}
var pourInCup = param.pourInCup || function(){
throw new Error('必须传递pourInCup方法')
}
var addCondiments = param.addCondiments || function(){
throw new Error('必须传递addcondiments方法')
}
var F = function(){}
F.prototype.init = function(){
boildWater()
brew()
pourInCup()
addCondiments()
}
return F
}
var Coffee = Beverage({
brew: function(){
console.log('用沸水冲泡咖啡')
},
pourInCup: function(){
console.log('把咖啡倒进杯子')
},
addCondiments: function(){
console.log('加糖和牛奶')
}
})
var coffee = new Coffee()
coffee.init() 总结
模版方法模式就是通过抽象父类来定义子类的方法和执行顺序,子类来完成具体方法的实现。在js中往往不需要严格通过继承来实现这一模式。
享元模式
定义:享元模式是一种用于性能优化的模式,享元模式的核心是运用共享技术来有效支持大量细粒度的对象。享元模式要求将对象划分为内部状态和外部状态。
- 内部状态存储于对象内部
- 内部状态可以被一些对象共享
- 内部状态独立于具体场景,通常不会改变
- 外部状态取决于具体场景,并根据场景而变化,外部状态不能被共享
共享模式的目标就是尽量减少共享对象的数量。将所有内部状态放置在一个共享对象中,外部状态在必要的时候被传入共享对象来组装成一个完整的对象。享元模式是一种用时间换空间的优化模式。文件上传例子
对于多文件上传,如果同时上传2000个文件,不考虑享元模式,那么你将new 2000个文件对象,这是很大的内存开销。下面用享元模式来进行编写代码。
首先明确,上传类型为内部状态,文件名和文件大小为外部状态
- 构造函数
var Upload = function(uploadType){ this.uploadType = uploadType } -
工厂进行对象实例化
var UploadFactory = (function(uploadType){ var createdFlyWeightObjs = {} return { create: function(uploadType){ if(createdFlyWeightObjs[uploadType]){ return createdFlyWeightObjs[uploadType] } return createdFlyWeightObjs[uploadType] = new Upload(uploadType) } } })() -
管理器封装外部状态
var uploadManager = (function(){ var uploadDatabase = {} return { add: function(id, uploadType, fileName, fileSize){ var flyWeightObj = UploadFactory.create(uploadType) uploadDatabase[id] = { fileName: fileName, fileSize: fileSize } return flyWeightObj }, setExternalState: function(id, flyWeight){ var uploadData = uploadDatabase[id] for(var i in uploadData){ flyWeightObj[i] = uploadData[i] } } } })()
对象池
对象池很好理解,在对象池中定义一定数量的对象,当需要的时候,直接从对象池中获取,而不需要去创建新对象,只有当对象池中的对象数量不满足需求时,就新创建一个对象,并放到对象池中。总结
享元模式是为解决性能问题而生的模式,通过区分内部状态和外部状态来减少对象的产生。
#笔记##设计##读书笔记#