JS设计模式——装饰器模式

什么是装饰器模式?

装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。

实例

拿最近比较火的吃鸡游戏(绝地求生:大逃杀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!)

输出: 除了方法装饰器,还有类装饰器,属性装饰器,参数装饰器等等。使用详情可以参照

经验分享 程序员 微信小程序 职场和发展