首页 > 编程语言 >【JS】167-JavaScript设计模式——装饰者模式

【JS】167-JavaScript设计模式——装饰者模式

时间:2022-10-13 15:36:17浏览次数:49  
标签:167 设计模式 对象 price JavaScript sale Sale getPrice 装饰

【JS】167-JavaScript设计模式——装饰者模式_ide

四、装饰者模式(Decorator Pattern)

1.概念介绍

装饰者模式(Decorator Pattern):在不改变原类和继承情况下,动态添加功能到对象中,通过包装一个对象实现一个新的具有原对象相同接口的新对象。

装饰者模式有以下特点:

  1. 添加功能时不改变原对象结构
  2. 装饰对象和原对象提供的接口相同,方便按照源对象的接口来使用装饰对象。
  3. 装饰对象中包含原对象的引用。即装饰对象是真正的原对象包装后的对象。

实际上,装饰着模式的一个比较方便的特征在于其预期行为的可定制和可配置特性。从只有基本功能的普通对象开始,不断增强对象的一些功能,并按照顺序进行装饰。

2.优缺点和应用场景

2.1优点

  • 装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。

2.2缺点

  • 多层装饰比较复杂。

2.3应用场景

  • 扩展一个类的功能。
  • 动态增加功能,动态撤销。

3.基本案例

我们这里实现一个基本对象 ​​sale​​​,可以通过 ​​sale​​​对象获取不同项目的价格,并通过调用 ​​sale.getPrice()​​方法返回对应价格。并且在不同情况下,用额外的功能来装饰它,会得到不同情况下的价格。

3.1创建对象

这里我们假设客户需要支付国家税和省级税。按照装饰者模式,我们就需要使用国家税和省级税两个装饰者来装饰这个 ​​sale​​对象,然后在对使用价格格式化功能的装饰者装饰。实际看起来是这样:

    1. ​​let sale = new Sale(100);​​
    2. ​​sale = sale.decorate('country');​​
    3. ​​sale = sale.decorate('privince');​​
    4. ​​sale = sale.decorate('money');​​
    5. ​​sale.getPrice();​​
    使用装饰者模式后,每个

    装饰都非常灵活,主要根据其装饰者顺序,于是如果客户不需要上缴国家税,代码就可以这么实现:

    1. ​​let sale = new Sale(100);​​
    2. ​​sale = sale.decorate('privince');​​
    3. ​​sale = sale.decorate('money');​​
    4. ​​sale.getPrice();​​

    3.2实现对象

    接下来我们需要考虑的是如何实现 ​​Sale​​对象了。

    实现装饰者模式的其中一个方法是使得每个装饰者成为一个对象,并且该对象包含了应该被重载的方法。每个装饰者实际上继承了目前已经被前一个装饰者进行装饰后的对象,每个装饰方法在 ​​uber​​(继承的对象)上调用同样的方法并获取值,此外还继续执行一些操作。

    ​uber​​​关键字类似Java的 ​​super​​​,它可以让某个方法调用父类的方法, ​​uber​​属性指向父类原型。

    即:当我们调用 ​​sale.getPrice()​​​方法时,会调用 ​​money​​​装饰者的方法,然后每个装饰方法都会先调用父对象的方法,因此一直往上调用,直到开始的 ​​Sale​​​构造函数实现的未被装饰的 ​​getPrice()​​方法。理解如下图:

    【JS】167-JavaScript设计模式——装饰者模式_ide_02

    我们这里可以先实现构造函数 ​​Sale()​​​和原型方法 ​​getPrice()​​:
    1. ​​function Sale (price){​​
    2. ​​ this.price = price || 100;​​
    3. ​​}​​
    4. ​​Sale.prototype.getPrice = function (){​​
    5. ​​ return this.price;​​
    6. ​​}​​

    并且装饰者对象都将以构造函数的属性来实现:

    1. ​Sale.decorators = {};​

    接下来实现 ​​country​​​这个装饰者并实现它的 ​​getPrice()​​,改方法首先从父对象的方法获取值再做修改:

    1. ​​Sale.decorators.country = {​​
    2. ​​ getPrice: function(){​​
    3. ​​ let price = this.uber.getPrice(); // 获取父对象的值​​
    4. ​​ price += price * 5 / 100;​​
    5. ​​ return price;​​
    6. ​​ }​​
    7. ​​}​​

    按照相同方法,实现其他装饰者:

    1. ​​Sale.decorators.privince = {​​
    2. ​​ getPrice: function(){​​
    3. ​​ let price = this.uber.getPrice();​​
    4. ​​ price += price * 7 / 100;​​
    5. ​​ return price;​​
    6. ​​ }​​
    7. ​​}​​
    8. ​​Sale.decorators.money = {​​
    9. ​​ getPrice: function(){​​
    10. ​​ return "¥" + this.uber.getPrice().toFixed(2);​​
    11. ​​ }​​
    12. ​​}​​

    最后我们还需要实现前面的 ​​decorate()​​​方法,它将我们所有装饰者拼接一起,并且做了下面的事情:
    创建了个新对象 ​​​newobj​​​,继承目前我们所拥有的对象( ​​Sale​​​),无论是原始对象还是最后装饰后的对象,这里就是对象 ​​this​​​,并设置 ​​newobj​​​的 ​​uber​​​属性,便于子对象访问父对象,然后将所有装饰者的额外属性复制到 ​​newobj​​​中,返回 ​​newobj​​​,即成为更新的 ​​sale​​对象:

    4.改造基本案例

    1. Sale.prototype.decorate = function(decorator){​​
    2. ​​ let F = function(){}, newobj,​​
    3. ​​ overrides = this.constructor.decorators[decorator];​​
    4. ​​ F.prototype = this;​​
    5. ​​ newobj = new F();​​
    6. ​​ newobj.user = F.prototype;​​
    7. ​​ for(let k in overrides){​​
    8. ​​ if(overrides.hasOwnProperty(k)){​​
    9. ​​ newobj[k] = overrides[k];​​
    10. ​​ }​​
    11. ​​ }​​
    12. ​​ return newobj;​​
    13. ​​}​​

    这里我们使用列表实现相同功能,这个方法利用JavaScript语言的动态性质,并且不需要使用继承,也不需要让每个装饰方法调用链中前面的方法,可以简单的将前面方法的结果作为参数传递给下一个方法。

    这样实现也有个好处,支持反装饰或撤销装饰,我们还是实现以下功能:

    1. ​​let sale = new Sale(100);​​
    2. ​​sale = sale.decorate('country');​​
    3. ​​sale = sale.decorate('privince');​​
    4. ​​sale = sale.decorate('money');​​
    5. ​​sale.getPrice();​​

    现在的 ​​Sale()​​构造函数中多了个装饰者列表的属性:

    1. ​​function Sale(price){​​
    2. ​​ this.price = (price > 0) || 100;​​
    3. ​​ this.decorators_list = [];​​
    4. ​​}​​

    然后还是需要实现 ​​Sale.decorators​​​,这里的 ​​getPrice()​​​将变得更简单,也没有去调用父对象的 ​​getPrice()​​,而是将结果作为参数传递:

    1. ​​Sale.decorators = {};​​
    2. ​​Sale.decorators.country = {​​
    3. ​​ getPrice: function(price){​​
    4. ​​ return price + price * 5 / 100;​​
    5. ​​ }​​
    6. ​​}​​
    7. ​​Sale.decorators.privince = {​​
    8. ​​ getPrice: function(price){​​
    9. ​​ return price + price * 7 / 100;​​
    10. ​​ }​​
    11. ​​}​​
    12. ​​Sale.decorators.money = {​​
    13. ​​ getPrice: function(price){​​
    14. ​​ return "¥" + this.uber.getPrice().toFixed(2);​​
    15. ​​ }​​
    16. ​​}​​

    而这时候父对象的 ​​decorate()​​​和 ​​getPrice()​​​变得复杂, ​​decorate()​​​用于追加装饰者列表, ​​getPrice()​​​需要完成包括遍历当前添加的装饰者一级调用每个装饰者的 ​​getPrice()​​方法、传递从前一个方法获得的结果:

    1. ​​Sale.prototype.decorate = function(decorators){​​
    2. ​​ this.decorators_list.push(decorators);​​
    3. ​​}​​
    4.
    5. ​​Sale.propotype.getPrice = function(){​​
    6. ​​ let price = this.price, name;​​
    7. ​​ for(let i = 0 ;i< this.decorators_list.length; i++){​​
    8. ​​ name = this.decorators_list[i];​​
    9. ​​ price = Sale.decorators[name].getPrice(price);​​
    10. ​​ }​​
    11. ​​ return price;​​
    12. ​​}​​

    5.对比两个方法

    很显然,第二种列表实现方法会更简单,不用设计继承,并且装饰方法也简单。
    案例中 ​​​getPrice()​​​是唯一可以装饰的方法,如果想实现更多可以被装饰的方法,我们可以抽一个方法,来将每个额外的装饰方法重复遍历装饰者列表中的这块代码,通过它来接收方法并使其成为“可装饰”的方法。这样实现, ​​sale​​​的 ​​decorators_list​​属性会成为一个对象,且该对象每个属性都是以装饰者对象数组中的方法和值命名。

    参考资料

    1. 《JavaScript Patterns》

    【JS】167-JavaScript设计模式——装饰者模式_装饰者模式_03

    标签:167,设计模式,对象,price,JavaScript,sale,Sale,getPrice,装饰
    From: https://blog.51cto.com/u_11887782/5753515

    相关文章

    • 【JS】169-JavaScript设计模式——外观模式
      六、外观模式(FacadePattern)1.概念介绍外观模式(FacadePattern) 是一种简单又常见的模式,它为一些复杂的子系统接口提供一个更高级的统一接口,方便对这些子系统的接口访问......
    • 【JS】168-JavaScript设计模式——策略模式
      五、策略模式(StrategyPattern)1.概念介绍策略模式(StrategyPattern):封装一系列算法,支持我们在运行时,使用相同接口,选择不同算法。它的目的是为了将算法的使用与算法的实现......
    • 【JS】166-JavaScript设计模式——迭代器模式
      三、迭代器模式(IteratorPattern)1.概念介绍迭代器模式(IteratorPattern) 是提供一种方法,顺序访问一个聚合对象中每个元素,并且不暴露该对象内部。这种模式属于行为型模式......
    • 【JS】172-JavaScript设计模式——观察者模式
      九、观察者模式(ObserverPatterns)1.概念介绍观察者模式(ObserverPatterns) 也称订阅/发布(subscriber/publisher)模式,这种模式下,一个对象订阅定一个对象的特定活动,并在状......
    • 【JS】170-JavaScript设计模式——代理模式
      七、代理模式(ProxyPattern)1.概念介绍代理模式(ProxyPattern) 为其他对象提供一种代理,来控制这个对象的访问,代理是在客户端和真实对象之间的介质。简单的理解:如我们需要......
    • 【JS】89-用JavaScript实现的5个常见函数
      前言    在学习 JavaScript,或者前端面试中,有人会问你节流函数、防抖函数、递归函数等,本文分享了5个常见函数,希望对你有所帮助。    在 JavaScript 中有一些问题......
    • 【JavaScript】13-JS中常见设计模式
      开发中,我们或多或少地接触了设计模式,但是很多时候不知道自己使用了哪种设计模式或者说该使用何种设计模式。本文意在梳理常见设计模式的特点,从而对它们有比较清晰的认知。Ja......
    • 设计模式之策略模式
      概述策略模式(StrategyPattern)的思想是在程序运行时动态改变某一个类的执行逻辑,属于一种行为型设计模式。目的是为了尽可能的减少if...else代码。它的核心在于”选择“两......
    • javascript学习
      1.JavaScript中的数据类型//在js中,可以分两种类型:基本类型和引用类型。【两者区别在于存储位置不同。】基本类型有:Number,String,Boolean,Undefined,null,symbol......
    • 史上最全!熬夜整理56个JavaScript高级的手写知识点!!专业扫盲!
      史上最全!熬夜整理56个JavaScript高级的手写知识点!!专业扫盲! Sunshine_Lin2021年10月28日08:27 ·  阅读56223本文已参与「掘力星计划」,赢取创作大礼包,挑战......