装饰器模式
装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。
这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。
意图:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。
主要解决:一般的,我们为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。
何时使用:在不想增加很多子类的情况下扩展类。
如何解决:将具体功能职责划分,同时继承装饰者模式。
一、装饰器形成过程
下面通过一个例子来实现装饰器形成过程:
1 函数直接调用,无任何修饰
class Log {
//普通函数
print(msg){
console.log(msg)
}
}
dec(Log,'print')
// 1 直接调用,无任何修饰
const log = new Log()
log.print('hello kicy')
2、装饰器: 普通函数调用,对Log进行了修饰
以上例子,对print()方法结果进行重组,想在这里执行的结果再传一个参数,本身的函数不能再传参了,解决办法—>升阶
引出一个概念:装饰器工厂
原来的是一个“装饰器函数“,变成了制造 ” 装饰器函数“ 的 ”函数“,所以它叫装饰器工厂
实现代码:
class Log {
print(msg){
console.log(msg)
}
}
// 装饰器工厂
const dec = (target,property) => {
const old = target.prototype.print
target.prototype[property] = msg => {
console.log('执行了' + property)
msg = `{${msg}}`
old(msg)
}
}
//修饰print
dec(Log,'print')
const log = new Log()
log.print('hello kicy')
**3 装饰器工厂(想有更多的定制,如:在dec函数上再传个参数,进一步修饰–>使用升阶方式–即高阶函数(因为装饰器本身没有再传参机会,所以要使用升阶的方式) **
const decUp = (name) => (target,property) => {
const old = target.prototype[property] //把原来属性拿出来,再写个新的
target.prototype[property] = msg => {
console.log('执行了:'+property)
msg = `进一步装饰: '${msg}: ${name}'`
old(msg)
}
}
//传递多个参数
decUp('kicy')(Log,'print')
//实例化
const log = new Log()
//调用执行
log.print('hello')
4 注解型装饰器
3这种写法不优雅,选择装饰器的写法 注解类型的装饰器(叫装饰器或者注解都可以)
@是装饰器的语法糖
实现原理:返回的是属性描述符,其实用的是definproperty
class Log2 {
@decorate
print(msg){
console.log(msg)
}
}
function decorate(target,property,descriptor){
const old = descriptor.value //descriptor.value从属性描述符种取出value就是属性本身, 获取方法本身,这里对应的是print()
descriptor.value = msg => {
msg = `装饰器: [${msg}]`
return old.apply(null,[msg])
}
return descriptor
}
const log2 = new Log2()
//调用执行
log2.print('hello2')
注解型装饰器实际的实现原理:它是通过descriptor属性描述符,最终返回decriptor这个描述符,其实就是使用defineProperty,再设置到对应的属性上就可以了
装饰器仿写,这个方法的最后一层封印 ,仿造的是上面@decorate的写法,装饰器内部实现的原理就是使用的defineProperty
传入一个修饰器decorate,方法内部对修饰器decorate进行一个实现,再用 definProperty 设置进去,这是对原来装饰器语法糖的简单模拟
const anotation = (target,property,decorate) => {
const decorator = decorate(target.prototype,property,Object.getOwnPropertyDescriptor(target.prototype,property))
Object.defineProperty(target.prototype,property,decorator)
}
anotation(Log,'print',decorate)
二、实战例子
定义 decto.ts
import * as glob from 'glob' //读取文件夹的文件,可以迭代
import * as Koa from 'koa'
import * as KoaRouter from 'koa-router'
const router = new KoaRouter()
//1 get和post重复性高,再升阶进行复用
// export const get = path => (target,property) => {
// router['get'](path,target[property])
// }
// export const post = path => (target,property) => {
// router['post'](path,target[property])
// }
//2 传入method,升阶
// export const method = method => (path:string,options?) => {
// return (target,property) => {
// const url = options && options.prefix ? options.prefix + path : path
// //router 在这里使用作用域传入,不符合传输透明原则,改为参数传进来
// router[method](path,target[property])
// }
// }
// export const get = method('get')
// export const post = method('post')
//3 传入router,再升阶
export const method = router => method => (path:string,options?) => {
return (target,property) => {
//注意:不同的中间件根据需求有不同的执行顺序(如token的需要先执行,因为要先获取daotoken,再往下执行其他的),通过nextTick方式调整
process.nextTick(()=>{
//注册中间件
const middlewares = []
console.log('target.middlewares',target.middlewares)
//获取class类中的中间件
if(target.middlewares){
middlewares.push(...target.middlewares)
}
if(options && options.middlewares){
middlewares.push(...options.middlewares)
}
middlewares.push(target[property])
const url = options && options.prefix ? options.prefix + path : path
//router 在这里使用作用域传入,不符合传输透明原则,改为参数传进来
// router[method](url,target[property])
router[method](url,...middlewares)
})
}
}
const decorate = method(router)
export const get = decorate('get')
export const post = decorate('post')
//中间件
export function middlewares(middlewares){
return function(target){
target.prototype.middlewares = middlewares
}
}
//load就是读入文件(这里是routes)列表
export const load = (folder:string) : KoaRouter => {
const extname = '.{js,ts}'
//glob 扫描所有的文件夹,相当于递归
//glob.sync同步读取, ./**/*${extname} 表示可以加载对应文件夹下无限层级的文件夹下的js/ts文件
glob.sync(require('path')
.join(folder,`./**/*${extname}`))
.forEach(item => require(item)); //依次加载,加载所有的class,它们会执行对应的装饰器语法,而装饰器语法会把多有的router里面的规则进行设置
return router
}