javascript 设计模式之装饰者模式

文章系列

javascript 设计模式之单例模式javascript

javascript 设计模式之适配器模式java

javascript 设计模式之装饰者模式git

javascript设计模式之代理模式github

javascript 适配、代理、装饰者模式的比较web

javascript 设计模式之状态模式npm

javascript 设计模式之迭代器模式编程

javascript 设计模式之策略模式设计模式

javascript 设计模式之观察者模式安全

javascript 设计模式之发布订阅者模式babel

概念

装饰者(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 中的装饰器

配置环境

  1. npm install babel-plugin-transform-decorators-legacy --save-dev
  2. 修改 .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 实现的,那该装饰方法的函数的三个参数都分别表示什么呢?

目前有个开源的第三方库 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.

复制代码

参考连接

JavaScript 设计模式核⼼原理与应⽤实践

结语

你的点赞是对我最大的确定,若是以为有帮助,请留下你的赞扬,谢谢!!!

相关文章
相关标签/搜索