设计模式——装饰者模式
我们给对象添加新的功能经常会使用继承,但继承存在着一些缺点。一方面会增强父子类之间的耦合性。另一方面,父类的细节对子类也是可见的,破坏了父类的封装性。装饰者模式提供了比继承更灵活的解决方案。和继承相比,它采取的是一种动态包装的方法添加新功能,在程序运行时再给对象根据需要添加职责。
实例演示
装饰者模式作为继承的替代方案,同样可以给子类添加行为,和继承不同的是新增的行为不会影响到父类,而是通过一层包装器来装饰类。一个利用装饰者模式生成不同配置的mac价格的简单实例如下:
funtion Macbook() {
this.cost = function() {
return 1000;
};
}
function Memory(macbook) {
this.cost = function() {
return macbook.cost() + 75;
};
}
function BlurayDrive(macbook) {
this.cost = function() {
return macbook.cost() + 300;
};
}
// 用法
let myMac = new BlurayDrive(new Memory(new Macbook()));
通过层层包装我们就成功实现了在原始类上添加新的信息。
装饰函数
在JS中我们可以很方便的实现装饰者模式,因为对象的属性和方法扩展很容易。但在代码运行期间,我们却很难给函数内添加一些额外装饰功能。要想实现对函数的装饰最简单的方法就是改写函数,但绝大多数情况我们都不想改动原函数。因为不仅违反了开放封闭原则,大多数时候可能原函数是由其他同事编写,改动后可能会出现意想不到的错误。我们需要在不改动源代码的情况下装饰函数。下面用面向切面编程的思想来为对函数使用装饰者模式。
我们知道一段函数的运行是从进入执行上下文开始的,当运行结束后就会跳出执行上下文回到执行环境继续往下运行。那么这个进入和退出两个阶段就是函数的切面。我们就可以针对这两个时机启动装饰函数。实例如下:
const before = function(fn, beforefn) { // 传入原函数和装饰函数
return function() {
beforefn.apply(this, arguments); // 保证this取值并在原函数执行前执行装饰函数
return fn.apply(this, arguments);
}
}
after函数的原理同上。
装饰函数应用实例
分离代码层次
面向切面编程应用的经典例子就是分离业务代码和杂务代码了。在项目开发中难免需要统计数据,比如有一个buttom点击后会弹出登录框,同时需要记录点击次数。由于登录业务和数据上报是两个层面的功能,我们就可以利用装饰函数将这两个层次的代码分离开来。
Function.prototype.after = function(afterfn) {
const _self = this;
return function() {
const ret = _self.apply(this, arguments);
afterfn.apply(this, arguments);
return ret;
}
};
let showLogin = function() {
console.log('登录');
};
let log = function() {
console.log('记录次数');
}
showLogin = showLogin.after(log); // 登录后上报数据
动态改变函数参数
有些时候可能因为某个业务的增加需要向函数中新增一些参数供他人调用,但同时原函数仍然在一些业务中有使用,所以不想更改原函数。这时候就可以在需要动态增加一些参数时使用装饰函数。
let func = function(param) {
console.log(param);
};
func = func.before(function(param) {
param.b = 'b';
});
func({a: 'a'}); // 输出{a: "a", b: "b"}
小结
装饰者模式是为已有功能进行锦上添花的一种设计模式,把要装饰的功能放在函数里再用函数包装已有函数对象,优点就在于把核心业务和装饰功能区分开来,使得代码的结构更有条理,并且可移植性更强。在今后实现应付PM一些与核心业务关系不大的小需求时,可以考虑装饰者模式是否适用,而不必深入内部函数去修改。
-- EOF --