其实每个设计模式的名字都非常准确的表达了中心思想,装饰器模式,那中心思想就是装饰,装饰什么呢,装饰我们原有的类或者方法,使类和方法拥有更多的能力和可能性,完成一些原本没能力完成的事情,或者说把原本的事情完成的更出色
我们看下装饰器模式的定义:
装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。
基础装饰器
装饰器,作为装饰,要做的就是要锦上添花,有的人会问,那我继承下来超类,自己再添加一些不同的方法和属性,或者把原方法和属性重写一些,也可以做到锦上添花的效果啊,那这里就有两点需要声明一下了
- 1.我们为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。
- 2.通过继承的方式会导致强耦合,超类的改变会导致所有的子类都跟随改变
我们通过最原生的基础来着手看一下装饰器模式
var Plane = {
fire: function () {
console.log('发射普通的子弹');
}
};
var missileDecorator= function () {
console.log('发射导弹!');
};
var fire = Plane.fire;
Plane.fire=function () {
fire();
missileDecorator();
};
Plane.fire();
//输出结果为,"发射普通的子弹" "发射导弹"
由示例可以看出,我们Plane类的fire方法做了装饰,调用依旧是原本的调用习惯,但是却多了一个"发射导弹",这也侧面看出装饰器的一个特点,
透明
,而且有一个优点,就是我们可以再装饰的fire中去判断要不要添加装饰,也就是通过条件判断是否要执行missileDecorator
钢铁侠示例
以钢铁侠示例,我们可以把原本的人作为原生的类,原生的人由什么能力,胳膊,腿,奔跑,拳击诸如此类的能力,但是我们可以通过装饰,也就是钢铁侠的套装,来给原本的人,来添加能力
//一个普通人类
class Person(){
constructor() {
this.name = 'Iron Man'
},
hand(){
console.log('我能抓取东西')
},
leg(){
console.log('我能奔跑')
}
}
//机械手装饰器
class Manipulator(){
constructor(person) {
this.oldHand= person.hand
Person.hand= this.hand
}
hand() {
this.oldHand()
console.log('发送冲击波')
}
}
//机械腿装饰器
class Mechanicalleg(){
constructor(person) {
this.oldLeg= person.leg
Person.leg= this.leg
}
leg() {
this.oldLeg()
console.log('我能飞了')
}
}
//普通人出现
var person= new Person()
person.hend()//我能抓取东西
//给普通人装饰机械手臂
var Mechanicalperson= new Manipulator(person)
Mechanicalperson.hend()//发送冲击波
//给普通人装饰机腿
var Mechanicalperson= new Mechanicalleg(person)
Mechanicalperson.leg()//我能飞了
有人会问,不能装完手装腿吗,只能用一个吗,当然不是的,装饰器可以多层装饰,当然也可以做一个机械套装的装饰,把两个装饰器组合一下,都是可以的,固定例子但是灵活运用才可以达到效果嘛
函数装饰封装
我们调用的函数有时候会有特定的需求,我们该如何让调用函数时自动调用另外一个函数,这样的能力装饰器也是可以完成的,无论是函数调用前的逻辑处理还是函数调用后的逻辑处理,都是可以完美实现的,我们看例子
Function.prototype.before=function (beforefn) {
var _this= this;
return function () {
beforefn.apply(this,arguments);
return _this.apply(this,arguments);
};
};
//使用举例,formSubmit和validata都为函数
formSubmit= formSubmit.before(validata);
- Function的prototype挂载before方法,其实跟装饰器的封装没有关系,我们前边也梳理过原型模式,总之就是为了让所有的函数都可以正常调用到before方法而已
- apply问题.这个是编程的基础,关于this的指向问题,和call都是一样的改变指针指向的作用,只不过是apply是接受数组形式的参数
- this指向问题,使用闭包储存了this指向,这个储存起来的this指向永远指向老函数,也就是调用before的函数的对象(例子中的formSubmit),看一下下边返回函数的内容,beforefn的this指向被临时改为返回函数的this,arguments为参数,保证beforefn的this不丢失,被正常调用,而后使用闭包的this调用formSubmit,也就达成了目的
ES7装饰器(语法糖)
前情提要:es7的装饰器属于语法糖 所以我们在项目中使用的时候要借助babel插件编译
ES7de装饰器的使用非常简单,其本质上就是一个函数
- 给类添加装饰
function leg(target, name, descriptor) {
console.log(target,name,descriptor)
}
@leg// 装饰类的装饰器
class Person{
leg() {
console.log('Person')
}
}
const person= new Person()
person.leg()
- 给方法添加装饰
function leg(target, name, descriptor) {
console.log(target,name,descriptor)
}
class Person{
@leg// 装饰方法的装饰器
leg() {
console.log('Person')
}
}
const person= new Person()
person.leg()
实际打印会发现,当给类添加装饰和给方法添加装饰的时候,参数是不相同的
- 在装饰类的时候,第一个参数表示类的函数本身,后两个参数为undefined
- 再装饰方法的时候,第一个参数表示类的原型(prototype), 第二个参数表示方法名, 第三个参数表示被装饰参数的属性
- 第三个参数内有如下属性:
configurable - 控制是不是能删、能修改 descriptor 本身。
writable - 控制是不是能修改值。
enumerable - 控制是不是能枚举出属性。
value - 控制对应的值,方法只是一个 value 是函数的属性。
get 和 set - 控制访问的读和写逻辑。
装饰器工厂
装饰器工厂,那顾名思义肯定是要产出不同的装饰器的,既然装饰器是可以接收参数的,那说明接收参数的装饰器就是可以产出不同的"自己"
@Test('hello')
class Hello {}
function Test(str) {
return function(){
target.prototype.a = str;
target.prototype.f = function(){
console.log(str)
};
}
}
let o = new Hello();
o.f() ==>"hello"
console.log(o.a) ==>"hello"
- 从最简单的模式来看,装饰器接收参数之后,通过return函数的方式来返回不同的装饰器,是完全可以做到的,这也就是装饰器工厂
- 同时ES6也有能力分别去添加新方法和重载旧方法,添加新方法上述的例子中就有,重载旧方法在末尾放一个,很好理解,也就不做赘述了
function Test(target) {
//通过extends方式来继承和重写被装饰类中的方法
return class extends target{
f(){
console.log('我是装饰器方法',this.a)
}
}
}
总结:在ES7中有装饰器的环境下,我们很容易就忽视了装饰器模式的的基本概念,封装的越好,就越是使我们的开发效率更高,但是长时间不关注底层原理,就会导致地基不牢,所以还是敞开怀抱接受最方便的封装,但是低下头一定不要忘了多看看基本的理念