上班族大多都有睡懒觉的习惯,每天早上上班时间都很紧张,于是很多人为了多睡一会,就会用方便的方式解决早餐问题。有些人早餐可能会吃煎饼,煎饼中可以加鸡蛋,也可以加香肠,但是不管怎么“加码”,都还是一个煎饼。在现实生活中,常常需要对现有产品增加新的功能或美化其外观,如房子装修、相片加相框等,都是装饰器模式。
在软件开发过程中,有时想用一些现存的组件。这些组件可能只是完成了一些核心功能。但在不改变其结构的情况下,可以动态地扩展其功能。所有这些都可以釆用装饰器模式来实现。
装饰器(Decorator)模式的定义:指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式,它属于对象结构型模式。
装饰器模式的主要优点有:
其主要缺点是:装饰器模式会增加许多子类,过度使用会增加程序得复杂性。
拿最近比较火的吃鸡游戏(绝地求生:大逃杀PUBG)来说,游戏中每个玩家降落到岛上,刚开始是一无所有的,需要通过捡拾或掠夺装备来武装自己,然后经过互相残酷的拼杀,获得游戏的胜利。
游戏过程中,我们可以把每一个玩家当成需要装饰的主类。其余的武器当成装饰类。玩家可以被任何武器装饰,从而获得不同的能力。
下面例子中,玩家主类分别通过手枪类和狙击步枪(Kar98)类修饰后,强化了自身的 fire 方法。获取了不一样的功能。与此同时,类里的其他方法 sayName 并没有受到装饰器的影响。
// 被装饰的玩家
class Player {
constructor(name) {
this.name = name
}
sayName() {
console.log(`I am ${this.name}`)
}
fire() {
console.log('I can only punch!')
}
}
// 装饰器——手枪
class Pistol {
constructor(player) {
player.fire = this.fire
}
fire() {
console.log('I shoot with my Pistol!')
}
}
//装饰器——Kar98狙击步枪
class Kar98 {
constructor(player) {
player.fire = this.fire
}
fire() {
console.log('I shoot with my Kar98!')
}
}
// 新玩家
const player = new Player('zkk')
//打招呼
player.sayName() // => 'I am zkk'
// 现在还没有武器,只会用拳头
player.fire() // => 'I can only punch!'
// 哎,捡到一个手枪,装饰上
const playerWithPistol = new Pistol(player)
// 发现敌人,用手枪开火
playerWithPistol.fire() // => 'I shoot with my Pistol!'
// 哇!捡到一个98K,装饰上
const playerWithKar98 = new Kar98(player)
// 用98k开火,奈斯!
playerWithKar98.fire() // => 'I shoot with my Kar98!'
通过实例,我们可以看出装饰器模式可以动态地给一个对象添加一些额外的功能,同时结构上更加灵活。
如果不使用装饰者模式,为了实现以上功能,我们就需要对玩家和各种武器的组合创建无数多的类,在需要的时候再去实例化。这样的管理是非常复杂的。
目前 TS 和 ES7 已经支持装饰器的使用,下面是新语法中方法装饰器的使用。
方法装饰器表达式会在运行时当作函数被调用,传入下列3个参数:
target——对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
name——成员的名字。
descriptor——成员的属性描述符。
// 定义kar98方法装饰器
let kar98 = (target, name, descriptor) => {
const oldValue = descriptor.value
// 装饰器中替代原先的fire方法
descriptor.value = function () {
oldValue.apply(null, arguments)
target.sayName()
console.log(`${name}功能被增强`)
console.log('I can fire with kar98!')
}
}
// 玩家类
class Player {
sayName() {
console.log('I am zkk')
}
// 用kar98装饰器装饰fire方法,就可以升级能力了。
@kar98
fire(s: string){
// 默认如果没有装饰器,只能拳击
console.log(s)
}
}
const player = new Player()
player.fire('I can punch!')