一、前言
所谓装饰者模式,就是动态的给类或对象增加职责的设计模式。它能在不改变类或对象自身的基础上,在程序的运行期间动态的添加职责。这种设计模式非常符合敏捷开发的设计思想:先提炼出产品的MVP(Minimum Viable Product,最小可用产品)
,再通过快速迭代的方式添加功能。
二、传统面向对象语言的实现方式
代码语言:javascript复制var Car = function() {}
Car.prototype.drive = function() {
console.log('乞丐版');
}
var AutopilotDecorator = function(car) {
this.car = car;
}
AutopilotDecorator.prototype.drive = function() {
this.car.drive();
console.log('启动自动驾驶模式');
}
var car = new Car();
car = new AutopilotDecorator(car);
car.drive(); //乞丐版;启动自动驾驶模式;
这种方式的实现要点是装饰器类要维护目标对象的一个引用,同时要实现目标类的所有接口(这个例子里的drive
方法,如果还有其它方法,比如brake
,AutopilotDecorator也要实现)。调用方法时,先执行目标对象原有的方法,再执行自行添加的特性。
当接口比较多,装饰器也比较多时,可以独立抽取一个装饰器父类,实现目标类的所有接口,再创建真正的装饰器来继承这个父类。
代码语言:javascript复制var Car = function() {}
Car.prototype.drive = function() {
console.log('乞丐版');
}
/* 多了一个刹车方法 */
Car.prototype.brake = function() {
console.log('刹车');
}
/* 实现所有接口的装饰器父类 */
var CarDecorator = function(car) {
this.car = car;
}
CarDecorator.prototype = {
drive: function() {
this.car.drive();
},
brake: function() {
this.car.brake();
}
}
/* 真正的装饰器 */
var AutopilotDecorator = function(car) {
CarDecorator.call(this, car);
}
AutopilotDecorator.prototype = new CarDecorator();
AutopilotDecorator.prototype.drive = function() {
this.car.drive();
console.log('启动自动驾驶模式');
}
/* 真正的装饰器 */
var HybridDecorator = function(car) {
CarDecorator.call(this, car);
}
HybridDecorator.prototype = new CarDecorator();
HybridDecorator.prototype.brake = function() {
this.car.brake();
console.log('启动充电模式');
}
var car = new Car();
car = new AutopilotDecorator(car);
car = new HybridDecorator(car);
car.drive(); //乞丐版;启动自动驾驶模式;
car.brake(); //刹车;启动充电模式;
三、JS基于对象的实现方式
代码语言:javascript复制var car = {
drive: function() {
console.log('乞丐版');
}
}
var driveBasic = car.drive;
var autopilotDecorator = function() {
console.log('启动自动驾驶模式');
}
var carToDecorate = Object.create(car);
carToDecorate.drive = function() {
driveBasic();
autopilotDecorator();
}
carToDecorate.drive(); //乞丐版;启动自动驾驶模式;
这种实现方式完全是基于JS自身的语言特点做考量。定义类的目的是实现代码的封装和复用,而JS这门语言是没有类的概念的。它只有2种数据类型:基本类型和对象类型。实现逻辑的封装和代码的重用只需要通过对象来组织代码,然后利用原生提供的克隆机制(Object.create
)来达到目的。
从代码的角度看,如果想扩展drive
方法,只需要用一个变量来保存原函数的引用,然后再重写drive
方法就可以了。在重写的方法里面,只要记得调用方法原有的行为就行。
另外,我们可以通过以下的工具函数,达到装饰函数的目的:
代码语言:javascript复制Function.prototype.after = function(afterfn) {
var _self = this;
return function() {
var ret = _self.apply(this, arguments);
afterfn.apply(this, arguments);
return ret;
}
}
var car = {
drive: function() {
console.log('乞丐版');
}
}
var autopilotDecorator = function() {
console.log('启动自动驾驶模式');
}
var carToDecorate = Object.create(car);
carToDecorate.drive = car.drive.after(autopilotDecorator);
carToDecorate.drive(); //乞丐版;启动自动驾驶模式;
通过在Function
的原型链上定义after函数,给所有的函数都赋予了被扩展的功能,当然也可以根据需要定义一个before
的函数,在函数执行前去做一些操作。这种实现方式借鉴了AOP(Aspect Oriented Programming,面向切面编程)
的思想。
四、ES7的实现方式
ES7提供了一种类似的Java注解的语法糖decorator
,来实现装饰者模式。使用起来非常简洁:
function autopilotDecorator(target, key, descriptor) {
const method = descriptor.value;
descriptor.value = () => {
method.apply(target);
console.log('启动自动驾驶模式');
}
return descriptor;
}
class Car {
@autopilotDecorator
drive() {
console.log('乞丐版');
}
}
let car = new Car();
car.drive(); //乞丐版;启动自动驾驶模式;
decorator
的实现依赖于ES5的Object.defineProperty
方法。defineProperty
所做的事情是为一个对象增加新的属性,或者更改某个已存在的属性。调用方式是Object.defineProperty(obj, prop, descriptor)
。
var o = {}; // 创建一个新对象
// 在对象中添加一个属性
Object.defineProperty(o, "name", {
value : "Dickens",
writable : true,
enumerable : true,
configurable : true
});
// 在对象中添加一个方法
Object.defineProperty(o, "sayHello", {
value : function() {
console.log('Hello, my name is: ', this.name)
},
writable : true,
enumerable : true,
configurable : true
});
o.sayHello() //Hello, my name is: Dickens
decorator
的参数跟defineProperty
是完全一样的,含义也类似,通过修改descripter,就能达到扩展功能的目的。
五、总结
本文介绍了装饰者模式的基本概念,并通过不同的实现方式来介绍使用方法。对于不同的使用方法,也作了比较透彻的解释,让大家不但知其然,还知其所以然。
装饰者模式是一种十分常用且功能强大的模式,利用ES7的语法糖,我们能用非常简洁的方式来表达装饰的意图,推荐大家在实际项目中用起来。