一个例子
假如我们现在想设计一个可以配置自行车的游戏,自行车由玩家自行配置,包括有没有前面的框,后面的座椅,照明灯,换挡器等。
常规情况下,我们应该把每一种零件配置都定义为类,然后加装一个配件,就去继承它。但是如果这样的话,我们需要定义非常多的类,并且会出现很多继承的情况,让我们的代码变得非常冗余。
我们希望用另一种更简单的方法,动态的去给自行车的类增加功能。
装饰者模式的作用
装饰者(decorator)模式是一种可以给对象动态地增加职责的方式,式能够在不改变对象自身的基础上,在程序运行期间给对象动态地添加职责。
跟继承相比,装饰者模式显得更加的轻量。
使用装饰者模式实现自行车配置
首先我们实现自行车的类
function Bicycle() {
//setting......
}
Bicycle.prototype.func = function(){
console.log("自行车的功能");
}
之后我们将不同的功能设计为不同的包装类,分别是车灯,车框,车后座,每一个包装类都增强了原始类的功能函数,以此达到继承的作用。
function LightingBicycle(bicycle) {
this.bicycle = bicycle;
}
LightingBicycle.prototype.func = function(){
this.bicycle.func();
console.log("发光");
}
function FrameBicycle(bicycle) {
this.bicycle = bicycle;
}
FrameBicycle.prototype.func = function(){
this.bicycle.func();
console.log("放置");
}
function SeatBicycle(bicycle) {
this.bicycle = bicycle;
}
SeatBicycle.prototype.func = function(){
this.bicycle.func();
console.log("搭载");
}
最后简单测试一下
var bicycle = new Bicycle();
bicycle = new LightingBicycle(bicycle);
bicycle = new FrameBicycle(bicycle);
bicycle = new SeatBicycle(bicycle);
bicycle.func();
//打印
//自行车的功能
//发光
//放置
//搭载
以上我们简单的实现了对类的包装,并且每一个新功能又是一个包装类,我们可以按需求来进行增强,非常的方便。除了装饰类,我们也可以装饰函数.
对函数的装饰
当我们想增强函数的功能的时候,我们通常会对函数进行直接的修改,但是如果函数非常的杂乱庞大,我们可以考虑使用装饰来增强它。
继续使用上一个自行车的例子,如果我们是单纯的想去额外增强一下func的功能,那我们可以先使用另外一个方法引用它,然后重写它
function Bicycle() {
//setting......
}
Bicycle.prototype.func = function(){
console.log("自行车的功能");
}
var _func = Bicycle.prototype.func;
Bicycle.prototype.func = function(){
_func();
console.log("新增的功能");
}
因为中间使用了其他的变量引用函数,所以我们在这里需要考虑this的指向。
比如我们想增强document.getElementById方法
var _getElementById = document.getElementById;
document.getElementById = function( id ){
alert (1);
return _getElementById( id ); // (1)
}
这段代码执行之后,会抛出错误,因为在使用getElementById这个方法的时候,其中的this是指向document的,当我们使用一个新的变量指向它的时候,实际上这个时候的this已经指向window了,所以要正确使用的话,我们需要在增强方法的调用中绑定一下this。
var _getElementById = document.getElementById;
document.getElementById = function(){
alert (1);
return _getElementById.apply( document, arguments );
}
ES7提供的装饰器
上面的装饰器方法在现在其实已经很少使用了,因为ES7提供了很方便强大的装饰器方法。
开启装饰器的配置,参考 blog.csdn.net/weixin_4276…
类装饰器
@testable
class MyTestableClass {
// ...
}
function testable(target) {
target.isTestable = true;
}
MyTestableClass.isTestable // true
上面代码中,@testable 就是一个装饰器。它修改了 MyTestableClass这 个类的行为,为它加上了静态属性isTestable。testable 函数的参数 target 是 MyTestableClass 类本身。
基本上,装饰器的行为就是下面这样。
@decorator
class A {}
// 等同于
class A {}
A = decorator(A) || A;
也就是说,装饰器是一个对类进行处理的函数。装饰器函数的第一个参数,就是所要装饰的目标类。
如果觉得一个参数不够用,可以在装饰器外面再封装一层函数。
function testable(isTestable) {
return function(target) {
target.isTestable = isTestable;
}
}
@testable(true)
class MyTestableClass {}
MyTestableClass.isTestable // true
@testable(false)
class MyClass {}
MyClass.isTestable // false
注意,装饰器对类的行为的改变,是代码编译时发生的,而不是在运行时。这意味着,装饰器能在编译阶段运行代码。也就是说,装饰器本质就是编译时执行的函数。
方法装饰器
相关知识:Object.defineProperty
装饰器不仅可以装饰类,还可以装饰类的属性。
Object.defineProperty()在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回这个对象。
语法: Object.defineProperty(obj, prop, descriptor)
- obj:操作的对象
- prop:被定义或者修改的属性名称
- descriptor:将被定义或修改的属性描述符
- 返回值:被传递给函数的对象
class Person {
@readonly
name() { return `${this.first} ${this.last}` }
}
上面代码中,装饰器 readonly 用来装饰“类”的name方法。
装饰器函数 readonly 一共可以接受三个参数。
- 第一个参数是 类的原型对象,装饰器的本意是要“装饰”类的实例,但是这个时候实例还没生成,所以只能去装饰原型(这不同于类的装饰,那种情况时target参数指的是类本身);
- 第二个参数是 所要装饰的属性名
- 第三个参数是 该属性的描述对象
function readonly(target, name, descriptor){
// descriptor对象原来的值如下
// {
// value: specifiedFunction,
// enumerable: false,
// configurable: true,
// writable: true
// };
descriptor.writable = false;
return descriptor;
}
readonly(Person.prototype, 'name', descriptor);
// 类似于
Object.defineProperty(Person.prototype, 'name', descriptor);
函数的方法装饰器可以做许多的事情,可以阅读一下core-decorators.js,是一个第三方模块,提供了几个常见的装饰器,通过它可以更好地理解装饰器
注意:装饰器只能用于类和类的方法,不能用于函数,因为存在函数提升。
参考:segmentfault.com/a/119000001…
最后
装饰者模式是为已有功能动态地添加更多功能的一种方式,把每个要装饰的功能放在单独的函数里,然后用该函数包装所要装饰的已有函数对象,因此,当需要执行特殊行为的时候,调用代码就可以根据需要有选择地、按顺序地使用装饰功能来包装对象。优点是把类(函数)的核心职责和装饰功能区分开了,减少了很多不必要的代码。