文章系列
概念
装饰者(decorator)模式,又名装饰器模式,能够在不改变对象自身的基础上,在程序运行期间给对像动态的添加职责。与继承相比,装饰者是一种更轻便灵活的做法。
就好比手机扣,有了手机扣会方便观看视频,但对于手机原先的所有功能,像是拍照仍然可以直接使用。手机扣只是起到锦上添花作用,并不一定要有。
为什么要使用装饰者模式
初始需求:
画个圆
实现:
class Circle {
draw() {
console.info('画圆')
}
}
let c = new Circle()
c.draw()
更改需求:
画个红色的圆
实现:
或许这时你二话不说,就是找到 Circle 的 draw 方法改成如下:
class Circle {
draw() {
console.info('画圆')
this.setRed()
}
setRed() {
console.info('设置红色边框')
}
}
let c = new Circle()
c.draw()
如果需求不改,这种实现方式倒也没问题。 但如果哪天经理说我要实现个绿色边框,是不是又得改成:
class Circle {
draw() {
console.info('画圆')
this.setGreen()
}
setRed() {
console.info('设置红色边框')
}
setGreen() {
console.info('设置绿色边框')
}
}
let c = new Circle()
c.draw()
这种方式存在两个问题:
- Circle 这个类既处理了画圆,又设置颜色,这违反了单一职责原则
- 每次要设置不同颜色,都动到了 Circle 类,并且更改了 draw 方法,这违背了开放封闭原则(对添加开放,对修改封闭)
为了新的业务需求不影响到原有功能,需要将旧逻辑与新逻辑分离:
class Circle {
draw() {
console.info('画圆')
}
}
class Decorator {
constructor(circle) {
this.circle = circle
}
draw() {
this.circle.draw()
this.setRedBorder()
}
setRedBorder() {
console.info('设置红色边框')
}
}
let c = new Circle()
let d = new Decorator(c)
d.draw()
此时如果要画别的颜色圆形,只要修改 Decorator 类即可。
如此一来,就实现了"只添加,不修改原有的类"的装饰者模式,使用 Decorator 的逻辑装饰了旧的圆形逻辑。 当然你仍然可以单纯只创建个不带颜色的圆形,用 c.draw() 即可
AOP 装饰函数
AOP(Aspect Oriented Programming)面向切面编程。把一些与核心业务逻辑无关的功能抽离出来,再通过“动态织入”方式掺入业务逻辑模块 与业务逻辑无关的功能通常包括日志统计、安全控制、异常处理等等。
首先我们要实现两个函数:
一个用来前置装饰,一个用来后置装饰:
Function.prototype.before = function(beforeFunc){
var that = this;
return function(){
beforeFunc.apply(this, arguments);
return that.apply(this, arguments);
}
}
Function.prototype.after = function(afterFunc){
var that = this;
return function(){
var ret = that.apply(this, arguments);
afterFunc.apply(this, arguments);
return ret;
}
}
以前置装饰 before 为例,调用 before 时,传入一个函数,这个函数即为新添加的函数,它装载了新添加的功能代码。
接着把当前的 this 保存起来,然后返回一个“代理”函数。这样在原函数调用前,先执行扩展功能的函数,而且他们共用同一个参数列表。
后置装饰与前置装饰基本类似,只是执行顺序不同
验证:
var foobar = function (x, y, z) {
console.log(x, y, z)
}
var foo = function (x, y, z) {
console.log(x / 10, y / 10, z / 10)
}
var bar = function (x, y, z) {
console.log(x * 10, y * 10, z * 10)
}
foobar = foobar.before(foo).after(bar)
foobar(1, 2, 3)
输出:
0.1 0.2 0.3
1 2 3
10 20 30
以上设置 before 与 after 的方法污染了原型,可以改成:
var before = function (fn, beforeFunc) {
return function () {
beforeFunc.apply(this, arguments)
return fn.apply(this, arguments)
}
}
var after = function (fn, afterFunc) {
return function () {
var ret = fn.apply(this, arguments)
afterFunc.apply(this, arguments)
return ret
}
}
var a = before(
function () {
alert(3)
},
function () {
alert(2)
}
)
a = before(a, function () {
alert(1)
})
a()
输出:
1
2
3
ES7 中的装饰器
配置环境
- npm install babel-plugin-transform-decorators-legacy --save-dev
- 修改 .babelrc 文件:
{
"presets":["es2015","latest"],
"plugins": ["transform-decorators-legacy"] // 加上插件支持 ES7 的装饰语法
}
装饰类
装饰类不带参数
// 装饰器函数,它的第一个参数是目标类
function classDecorator(target) {
target.hasAdd = true
// return target // 可有可无, 默认就是返回 this 的
}
// 将装饰器"安装"到 Button 类上
@classDecorator
class Button {}
// 验证装饰器是否生效
alert('Button 是否被装饰了:' + Button.hasAdd)
等价于
function classDecorator(target) {
target.hasAdd = true
return target // 此时一定要用, 因为这时是作为函数使用,而非构造函数
}
class Button {}
Button = classDecorator(Button)
// 验证装饰器是否生效
alert('Button 是否被装饰了:' + Button.hasAdd)
说明装饰器的原理:
@decorator
class A{}
//等同于
A = decorator(A) || A
表明 ES7 中的装饰器也是个语法糖
装饰类带参数
// 装饰器要接收参数时,就要返回个函数,该函数的第一个参数是目标类
function classDecorator(name) {
return function (target) {
target.btnName = name
}
}
// 将装饰器"安装"到 Button 类上
@classDecorator('登录')
class Button {}
// 验证装饰器是否生效
alert('按钮名称:' + Button.btnName)
等同于
// 装饰器要接收参数时,就要返回个函数,该函数的第一个参数是目标类
function classDecorator(name) {
return function (target) {
target.btnName = name
return target
}
}
// 将装饰器"安装"到 Button 类上
class Button {}
Button = classDecorator('登录')(Button)
// 验证装饰器是否生效
alert('按钮名称:' + Button.btnName)
装饰类 - mixin 示例
function mixin(...list) {
console.info(...list, 'list')
// ...list 是个对象, key 为 "foo",值为 function() { alert('foo')}
return function (target) {
Object.assign(target.prototype, ...list)
console.dir(target, 'target')
}
}
const Foo = {
foo() {
alert('foo')
}
}
@mixin(Foo)
class Button {}
let d = new Button()
d.foo()
可以看到是往 Button 类的原型上加上了 foo 函数,那可能有人会问了,为什么不在类上直接加呢,即
function mixin(...list) {
console.info(...list, 'list') // ...list 是个对象, key 为 "foo",值为 function() { alert('foo')}
return function (target) {
Object.assign(target, ...list)
console.dir(target, 'target')
}
}
const Foo = {
foo() {
alert('foo')
}
}
@mixin(Foo)
class Button {}
let d = new Button()
d.foo()
此时会报Uncaught TypeError: d.foo is not a function
错误
这是由于实例是在代码运行时动态生成的,而装饰器函数则是在编译阶段就执行了,所以装饰器 mixin 函数执行时, Button 实例还不存在。
为了确保实例生成后可以顺利访问到被装饰好的方法(foo),装饰器只能去修饰 Button 类的原型对象。
装饰方法
readonly 示例
function readonly(target, name, descriptor) {
descriptor.writable = false
return descriptor
}
class Person {
constructor() {
this.first = 'A'
this.last = 'B'
}
@readonly
name() {
return `${this.first} ${this.last}`
}
}
let p = new Person()
console.info(p.name())
// p.name = function () {
// console.info(100)
// } // 修改会报错
log 示例
function log(target, name, descriptor) {
let oldValue = descriptor.value
descriptor.value = function () {
console.log(`calling ${name} width`, arguments)
return oldValue.apply(this, arguments)
}
return descriptor
}
class Math {
@log
add(a, b) {
return a + b
}
}
let math = new Math()
const result = math.add(4, 6)
alert(result)
装饰方法总结
以上 readonly 与 log 都是通过修改 descriptor 实现的,那该装饰方法的函数的三个参数都分别表示什么呢?
- target 表示目的对象,跟装饰类一样的
- name 表示修饰的方法名,比如上面的 log 装饰器里的 name 就是为 "add"
- descriptor 等同于
Object.defineProperty(obj, prop, descriptor)
里的 descriptor
目前有个开源的第三方库 core-decorators,提供了很多好用的装饰方法。
// import { readonly } from 'core-decorators'
// class Person {
// @readonly
// name() {
// return 'zhang'
// }
// }
// let p = new Person()
// alert(p.name())
// // p.name = function () { /*...*/ } // 此处会报错
import { deprecate } from 'core-decorators'
class Person {
@deprecate
facepalm() {}
@deprecate('We stopped facepalming')
facepalmHard() {}
@deprecate('We stopped facepalming', {
url: 'http://knowyourmeme.com/memes/facepalm'
})
facepalmHarder() {}
}
let person = new Person()
person.facepalm()
// DEPRECATION Person#facepalm: This function will be removed in future versions.
person.facepalmHard()
// DEPRECATION Person#facepalmHard: We stopped facepalming
person.facepalmHarder()
// DEPRECATION Person#facepalmHarder: We stopped facepalming
//
// See http://knowyourmeme.com/memes/facepalm for more details.
参考链接
结语
你的点赞是对我最大的肯定,如果觉得有帮助,请留下你的赞赏,谢谢!!!