从实例中入手
我们就拿《head first 设计模式》中的一个例子入手,使用面向对象的编程思想:
我们有一家咖啡店,暂定每杯咖啡售价10元
function Coffee() {
this.price = 10
}
Coffee.prototype.getPrice = function () {
return this.price
}
var coffee = new Coffee()
console.log(coffee.getPrice());
现在新进了一批配料(摩卡,蓝莓,奶泡)分别对应价钱为(5,3,4),那么我们要怎么计算一杯摩卡咖啡的价钱呢?
1.新建一个摩卡咖啡的对象:
function MochaCoffee() {
this.price = 15
}
MochaCoffee.prototype.getPrice = function () {
return this.price
}
var mochaCoffee = new MochaCoffee()
console.log(mochaCoffee.getPrice());
2.使用继承
var coffee = new Coffee()
function MochaCoffee() {
this.mochaPrice = 5
}
MochaCoffee.prototype = new Coffee()
MochaCoffee.prototype.getPrice = function () {
return this.price + this.mochaPrice
}
var mochaCoffee = new MochaCoffee()
console.log(mochaCoffee.getPrice());
那么每增加一种配料,就需要生成多一个类。
但是,无论是第一种办法,还是第二种办法都很难满足一下的情况,比如
- 如果我需要一杯摩卡加蓝莓的咖啡,那么只能再生成一个新的类(MochaBlueberryCoffee)
- 如果我需要一杯两倍蓝莓的咖啡呢? 难道要生成一个新的类叫(DoubelMochaBlueberryCoffee)吗?
目前只有3种配料,我们就需要添加3个新的类了,那么当我们的配料添加,或者添加(咖啡根据大中小杯去计算价钱)这种需求时,就需要添加无数的类去实现它;
如果只是使用继承,那么当情况复杂时,类的数量就会呈爆炸式增长,这是我们所不希望见到的。
装饰者模式登场
使用构造器存储coffee对象的引用
function Mocha(coffee) {
this.price = 5
this.coffee = coffee
}
Mocha.prototype.getPrice = function () {
return this.price + this.coffee.getPrice()
}
var coffee = new Coffee()
var mochaCoffee = new Mocha(coffee)
console.log(mochaCoffee.getPrice());
如果这时我们需要点一份双倍摩卡的咖啡,直接如下调用:
var coffee = new Coffee()
var mochaCoffee = new Mocha(coffee)
var doubelMochaCoffee = new Mocha(mochaCoffee)
console.log(doubelMochaCoffee.getPrice());
使构造器存储coffee对象的引用一点也不灵活,我们做如下改动:
function Mocha() {
this.price = 5
}
Mocha.prototype.getPrice = function () {
return this.price + this.coffee.getPrice()
}
Mocha.prototype.setCoffee = function (coffee) {
this.coffee = coffee
}
var coffee = new Coffee()
var mochaCoffee = new Mocha()
mochaCoffee.setCoffee(coffee)
console.log(mochaCoffee.getPrice());
现在就可以动态的设置coffee对象了!
如果你还想要一杯摩卡蓝莓咖啡也是非常容易实现的:
function Blueberry() {
this.price = 3
}
Blueberry.prototype.getPrice = function () {
return this.price + this.coffee.getPrice()
}
Blueberry.prototype.setCoffee = function (coffee) {
this.coffee = coffee
}
var coffee = new Coffee()
var mochaCoffee = new Mocha()
mochaCoffee.setCoffee(coffee)
var mochaBlueberryCoffee = new Blueberry()
mochaBlueberryCoffee.setCoffee(mochaCoffee)
console.log(mochaBlueberryCoffee.getPrice());
简单应用
window.onload = function () {
alert(1)
}
var _onload = window.onload
window.onload = function () {
_onload()
alert('new')
}
这是实际开发中很常见的一种做法,比如我们想给 window 绑定 onload 事件,但是又不确定 这个事件是不是已经被其他人绑定过,为了避免覆盖掉之前的 window.onload 函数中的行为,我 们一般都会先保存好原先的 window.onload,把它放入新的 window.onload 里执行,添加了一层简单的包装。
但是上面的做法会导致this丢失的问题,这里不会报错是因为全局变量就是window;
使用AOP装饰函数
分别实现Function.prototype.before
和Function.prototype.after
,如下:
Function.prototype.before = function (beforefn) {
var __self = this; // 保存原函数的引用
return function () { // 返回包含了原函数和新函数的"代理"函数
beforefn.apply(this, arguments); // 执行新函数,且保证 this 不被劫持,新函数接受的参数 // 也会被原封不动地传入原函数,新函数在原函数之前执行
return __self.apply(this, arguments); // 执行原函数并返回原函数的执行结果, 2 // 并且保证 this 不被劫持
}
}
Function.prototype.after = function (afterfn) {
var __self = this;
return function () {
var ret = __self.apply(this, arguments); afterfn.apply(this, arguments);
return ret;
}
};
在实际中的应用:
数据统计上报功能:
<html>
<button tag="login" id="button">点击打开登录浮层</button>
<script>
var showLogin = function(){
console.log( '打开登录浮层' );
log('上报信息');
}
var log = function( tag ){
console.log( '上报信息为: ' + tag );
}
document.getElementById( 'button' ).onclick = showLogin;
</script>
</html>
我们看到在 showLogin 函数里,既要负责打开登录浮层,又要负责数据上报,这是两个层面 的功能,在此处却被耦合在一个函数里。使用 AOP 分离之后,代码如下:
<html>
<button tag="login" id="button">点击打开登录浮层</button>
<script>
Function.prototype.after = function( afterfn ){
var __self = this;
return function(){
var ret = __self.apply( this, arguments ); 9 afterfn.apply( this, arguments );
return ret;
}
};
var showLogin = function(){
console.log( '打开登录浮层' );
}
var log = function(){
console.log( '上报信息为: ' + 'balabalabla' );
}
showLogin = showLogin.after( log ); // 打开登录浮层之后上报数据
document.getElementById( 'button' ).onclick = showLogin;
</script>
</html>
本质
在《设计模式》成书之前,GoF原想把装饰者(decorator)模式称为包装器(wrapper)模式。
其实装饰者模式,就是在原有的基础上添加了一层包装,形成一条包装链。
使用了装饰者模式,我们可以动态的添加被装饰者的职责,对它进行非入侵式的行为修改,从而达到我们要求而不需要创建更多的类。
缺点:当装饰的层数过深时,实际上作用域链也就变的很长,性能也会受到影响。
参考资料
- 《head first 设计模式》
- 《JavaScript设计模式与实践》
- 《JavaScript设计模式》