装饰者模式的定义:
装饰者(decorator)模式能够在不改变对象自身的基础上,在程序运行期间给对像动态的添加职责。与继承相比,装饰者是一种更轻便灵活的做法。
装饰者模式的特点:
可以动态的给某个对象添加额外的职责,而不会影响从这个类中派生的其它对象;
继承的一些缺点:
继承会导致超类和子类之间存在强耦合性,当超类改变时,子类也会随之改变;
超类的内部细节对于子类是可见的,继承常常被认为破坏了封装性;
//原始的飞机类
var Plan = function(){};
Plan.prototype.fire = function(){
console.log('发射普通子弹,');
};
//装饰类
var MissileDecorator = function(plan){
this.plan = plan;
}
MissileDecorator.prototype.fire = function () {
this.plan.fire();
console.log('发射导弹!');
};
var plan = new MissileDecorator( new Plan() );
plan.fire();//发射普通子弹,发射导弹!
ES7的装饰器decorator
decorator装饰类
@testable
class MyTestableClass {
//...
}
function testable(target) {
target.isTestable = true;
}
MyTestableClass.isTestable // true
@testable就是一个修饰器。它修改了MyTestableClass这个类的行为,为它加上了静态属性isTestable。testable函数的参数target是MyTestableClass类本身。
如果觉得一个参数不够用,可以在修饰器外面再封装一层函数。
function testable(isTestable) {
return function(target) {
target.isTestable = isTestable;
}
}
@testable(true)
class MyTestableClass {}
MyTestableClass.isTestable // true
@testable(false)
class MyClass {}
MyClass.isTestable // false
上面代码中,修饰器testable可以接受参数,这就等于可以修改修饰器的行为。
注意,修饰器对类的行为的改变,是代码编译时发生的,而不是在运行时。这意味着,修饰器能在编译阶段运行代码。也就是说,修饰器本质就是编译时执行的函数。
前面的例子是为类添加一个静态属性,如果想添加实例属性,可以通过目标类的prototype对象操作。
function testable(target) {
target.prototype.isTestable = true;
}
@testableclass
MyTestableClass {}
let obj = new MyTestableClass();
obj.isTestable // true
上面代码中,修饰器函数testable是在目标类的prototype对象上添加属性,因此就可以在实例上调用。修饰类重写第一示例:
function MissileDecorator(target){
let method = target.prototype.fire;
target.prototype.fire = function(...args){
method.apply(target.prototype,...args);
onsole.log('发射导弹!');
}
}
//修饰类
@MissileDecorator
class Plan{//原始的飞机类
fire(){
console.log('发射普通子弹,');
}
}
new Plan().fire();//发射普通子弹,发射导弹!
decorator装饰方法
修饰器不仅可以修饰类,还可以修饰类的属性方法。
class Person {
@readonly
name() {
return `${this.first} ${this.last}`
}
}
上面代码中,修饰器readonly用来修饰“类”的name方法。
修饰器函数readonly一共可以接受三个参数。
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);
修饰器第一个参数是类的原型对象,上例是Person.prototype,修饰器的本意是要“修饰”类的实例,但是这个时候实例还没生成,所以只能去修饰原型(这不同于类的修饰,那种情况时target参数指的是类本身);第二个参数是所要修饰的属性名,第三个参数是该属性的描述对象。修饰器Decorators的本质是利用了ES5的Object.defineProperty属性,这三个参数其实是和Object.defineProperty参数一致的。修饰方法重写第一示例:
function MissileDecorator(target,name,descriptor){
let method = descriptor.value;
descriptor.value = function(...args){
method.apply(target,...args);
console.log('发射导弹!');
}
}
//原始的飞机类
class Plan{
//装饰方法
@MissileDecorator
fire(){
console.log('发射普通子弹,');
}
}
new Plan().fire();//发射普通子弹,发射导弹!
淘宝前端团钢铁侠示例
首先创建一个普通的Man类,它的抵御值 2,攻击力为 3,血量为 3;
然后我们让其带上钢铁侠的盔甲,这样他的抵御力增加 100,变成 102;
让其带上光束手套,攻击力增加 50,变成 53;
最后让他增加“飞行”能力
//装配盔甲(类方法修饰)
function decorateArmour(target, key, descriptor){
const method = descriptor.value;
let moreDef = 100;
let ret;
descriptor.value = (...args)=>{
args[0] += moreDef;
ret = method.apply(target, args);
return ret;
}
return descriptor;
}
//光束手套(类方法修饰)
function decorateLight(target, key, descriptor) {
const method = descriptor.value;
let moreAtk = 50;
let ret;
descriptor.value = (...args)=>{
args[1] += moreAtk;
ret = method.apply(target, args);
return ret;
}
return descriptor;
}
//增加飞行能力(修饰类)
function addFly(canFly){
return function(target){
target.canFly = canFly;
let extra = canFly ? '(技能加成:飞行能力)' : '';
let method = target.prototype.toString;
target.prototype.toString = (...args)=>{
return method.apply(target.prototype,args) + extra;
}
return target;
}
}
@addFly(true)
class Man{
constructor(def = 2,atk = 3,hp = 3){
this.init(def,atk,hp);
}
@decorateArmour
@decorateLight
init(def,atk,hp){
this.def = def; // 防御值
this.atk = atk; // 攻击力
this.hp = hp; // 血量
}
toString(){
return `防御力:${this.def},攻击力:${this.atk},血量:${this.hp}`;
}
}
var tony = new Man();
console.log(`当前状态 ===> ${tony}`);//当前状态 ===> 防御力:102,攻击力:53,血量:3(技能加成:飞行能力)
装饰模式有两种:纯粹的装饰模式和半透明的装饰模式。
上述修饰类方法应该是纯粹的装饰模式,它并不增加对原有类的接口;修饰类相当于给类新增一个方法,属于半透明的装饰模式。
推荐阅读:
http://taobaofed.org/blog/2015/11/16/es7-decorator/
https://github.com/zhiqiang21/blog/issues/17
http://es6.ruanyifeng.com/#docs/decorator