装饰者模式

13次阅读

共计 1462 个字符,预计需要花费 4 分钟才能阅读完成。

简介

装饰者模式可以动态地给一个对象添加行为,相对通过继承来扩展功能,它是一个灵活的选择。装饰者模式也使用了继承,只是为了统一接口,如果去除 CDecorator 和 CComponent 的继承关系,就有些像桥接模式了。主要区别是装饰者不需要动态的改变被装饰的对象,而桥接模式是多个维度独立的变化。

UML 类图

示例

陈奕迅有一首歌,last order,其中有句歌词,“麻烦你 加冰威士忌 对不起 来个 DOUBLE 的”。在这里我们假定威士忌是已经实现的类,不能再改动了。那就需要装饰者登场了。这里实现了加冰威士忌,也实现了来个 double 的。

装饰类,decorator.h

#include <iostream>
using namespace std;

class CWine
{
public:
    virtual string GetName() = 0;
    virtual double GetCost() = 0;};

class CWhisky:public CWine
{
public:
    string GetName()
    {return "Whisky";}
    double GetCost()
    {return 60.0;}
};

class CWineDecorator:public CWine
{
public:
    CWineDecorator(CWine* pWine):m_pWine(pWine){};
    string GetName()
    {return m_pWine->GetName();
    }
    double GetCost()
    {return m_pWine->GetCost();
    }
public:
    CWine* m_pWine;
};

class CIceDecorator:public CWineDecorator
{
public:
    CIceDecorator(CWine* pWine):CWineDecorator(pWine){}
    string GetName()
    {return m_pWine->GetName().append("+Ice");
    }
    double GetCost()
    {return m_pWine->GetCost() + 10;
    }
};

客户端调用,main.cpp

#include "decorator.h"

#define SAFE_DELETE(p) if(p){delete (p); (p) = NULL;}
int main(int argc, char* argv[])
{
    CWine* pWhisky = new CWhisky;
    CWine* pIceWhisky = new CIceDecorator(pWhisky);
    std::cout<<"Name:"<<pIceWhisky->GetName().c_str()<<endl;
    std::cout<<"Price:"<<pIceWhisky->GetCost()<<endl;

    CWine* pDoubleIceWhisky = new CIceDecorator(pIceWhisky);
    std::cout<<"Name:"<<pDoubleIceWhisky->GetName().c_str()<<endl;
    std::cout<<"Price:"<<pDoubleIceWhisky->GetCost()<<endl;

    SAFE_DELETE(pDoubleIceWhisky);
    SAFE_DELETE(pWhisky);
    SAFE_DELETE(pIceWhisky);
    return 0;
}

输出结果如下:

Name:Whisky+Ice
Price:70
Name:Whisky+Ice+Ice
Price:80

正文完
 0

装饰者模式

13次阅读

共计 995 个字符,预计需要花费 3 分钟才能阅读完成。

装饰者模式

装饰器模式: 动态地给函数赋能。

JavaScript 的装饰者模式

生活中的例子: 天气冷了, 就添加衣服来保暖;天气热了, 就将外套脱下;这个例子很形象地含盖了装饰器的神韵, 随着天气的冷暖变化, 衣服可以动态的穿上脱下。

 let wear = function(){console.log('穿上第一件衣服')
   }
   const _wear1 = wear
   wear = function(){_wear1()
     console.log('穿上第二件衣服')
   }
   const _wear2 = wear
   wear = function(){_wear2()
     console.log('穿上第三件衣服')
   }
   wear()
// 穿上第一件衣服
// 穿上第二件衣服
// 穿上第三件衣服
这种方式有以下缺点: 1: 临时变量会变得越来越多;2: this 指向有时会出错 

AOP 装饰函数

    // 前置代码
    Function.prototype.before = function(fn){
      const self = this
      return function(){fn.apply(self,arguments)
        return self.apply(self,arguments)
      }
    }
    // 后置代码
   Function.prototype.after = function(fn){
     const self = this
     return function(){self.apply(self,arguments)
       return fn.apply(self,arguments)
     }
   }
  //  用后置代码来试验下 上面穿衣服的 demo
  const wear1 = function(){console.log('穿上第一件衣服')
  }
  const wear2 = function(){console.log('穿上第二件衣服')
  }
  const wear3 = function(){console.log('穿上第三件衣服')
  }
 const wear = wear1.after(wear2).after(wear3)
 wear()

但这样子有时会污染原生函数, 可以做点通变

   const after = function(fn,afterFn){return function(){fn.apply(this,arguments)
      afterFn.apply(this,arguments)
    }
   }
   const wear = after(after(wear1,wear2),wear3)
   wear()

正文完
 0

装饰者模式

13次阅读

共计 3571 个字符,预计需要花费 9 分钟才能阅读完成。

装饰者模式

装饰者模式 动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。

思维模式

首先需要明确一点:装饰者模式利用了 “组合” 的思想,避免了“继承”的滥用。
通过 动态地组合对象,可以写新的代码添加新功能,而无需修改现有代码。

这就好比拼装一个机器人。
我们有各种各样的零部件:

  1. 躯干
  2. 类人的胳膊
  3. 带有钳子的胳膊
  4. 类人的腿
  5. 带轮子的腿
  6. 带履带的腿
  7. ……

简单粗暴的实现

想要设计各式各样的机器人,当然我们可以选择,设计一个 Robot 超类,然后让子类继承这个超类。
这样,我们可以得到:

  1. 躯干 + 人的胳膊 + 履带腿
  2. 躯干 + 钳子胳膊 + 轮子腿
  3. 躯干 + 4 个人的胳膊 + 履带腿
  4. ……

根据排列组合的原理,如果我们需要,Robot的子类可以有无限多个。
假如我们从 Robot 父类处继承了一个 cost() 方法,这个方法根据该子类的 component(组件) 个数和数量来计算成本 (cost),那么我们的cost() 方法可以说要重写无数次——因为每一个子类的情况都是不一样的。

“装饰者模式”的实现

我们可以先为每一个零部件 (component) 确定成本,然后根据需要,动态地 组装(组合)一个机器人。然后将所有的成本加起来。如此一来,不仅可以得到一个符合我们需求的 机器人对象,更能很方便地计算 cost。

  1. 躯干 $100
  2. 类人的胳膊 $40
  3. 带有钳子的胳膊 $50
  4. 类人的腿 $70
  5. 带轮子的腿 $70
  6. 带履带的腿 $50
  7. ……

组合一个有 躯干 + 类人的胳膊 + 带履带的腿 的机器人的 cost 也就是:
100 + 40 + 50 = $190;

实现 组合

所谓 组合 ,也就是 我中有你

如图所示:


通过保有各个对象的引用,即可实现“组合”

对于这样一个组合来的 Robot 对象,我们不妨称其为:“robotTom”。
想要求得总成本,可以直接调用robotTom.cost();

这是怎样实现的呢?
如图所示:

用公式来表示就是:

[robotTom].cost() 
= [arm + body].cost() + leg.cost() 
= [body].cost() + arm.cost() + leg.cost() 
=  body.cost() + arm.cost() + leg.cost();

这里用“leg 对象包住 arm 对象”来形容 leg 对象中有 arm 对象的引用,不过对于一个机器人而言,“leg 能包住 arm”好像有点魔幻现实的味道。

再举一例

我们不妨再举一个更容易理解的例子:

我们知道,各类绘图软件中都有图层 (layer) 的概念。
每一个图层都相当于一层透明纸,我们可以在上面任意地画东西,而不会影响其他图层。
把画着东西的图层一层一层地叠放 (相当于“包装”) 起来,我们就可以得到各式各样的画作。
此时,位于上一层的图层就相当于 装饰者 ,而所谓的背景图层就相当于 被装饰者

类图


其中,

  1. LayerLayerDecorator为抽象类
  2. LayerDecorator本质上也是Layer
  3. description是各个 layer 对象的 自我描述
  4. position()表示 layer 对象在某个地方画一个图形
  5. RedBackgroundLayerBlueBackgroundLayer相当于 被装饰者

对应的代码:
Layer.java:

public abstract class Layer {

    String description = "我是抽象 layer 父类";

    public String getDescription() {return description;}

    public abstract String position();}

RedBackgroundLayer.java

public class RedBackgroundLayer extends Layer {public RedBackgroundLayer (){
        // 从抽象父类 layer 继承来的 description
        description = "我是 RedBackgroundLayer->" ;
    }

    @Override
    public String position() {return "我在底层画一个红色的 layer->";}

}

BlueBackgroundLayer.java

public class BlueBackgroundLayer extends Layer {public BlueBackgroundLayer() {description = "我是一个 BlueBackgroundLayer";}


    @Override
    public String position() {return null;}
}

各个装饰者:
RectangleLayerDecorator.java

public class RectangleLayerDecorator extends LayerDecorator {

    Layer layer; 

    //constructor
    public RectangleLayerDecorator(Layer layer) {// 这一步很重要
        this.layer = layer;
    }

    @Override
    public String getDescription() {return layer.getDescription() + "RectangleLayerDecorator->";
    }

    @Override
    public String position() {return layer.position() + "左上角画□->";
    }
}

TriangleLayerDecorator.java

public class TriangleLayerDecorator extends LayerDecorator{

    Layer layer;

    //constructor
    public TriangleLayerDecorator(Layer layer) {this.layer = layer;}

    @Override
    public String getDescription() {return layer.getDescription() + "TriangleLayerDecorator->";
    }

    @Override
    public String position() {return layer.position() + "右上角画△->";
    }
}

RoundLayerDecorator.java

public class RoundLayerDecorator extends LayerDecorator {

    Layer layer;
    //constructor
    public RoundLayerDecorator(Layer layer) {this.layer = layer;}

    @Override
    public String getDescription() {return layer.getDescription() + "RoundLayerDecorator->";
    }

    @Override
    public String position() {return layer.position() + "右下角画○->";
    }
}

运行 Demo

RunDemoTest.java

public class RunDemoTest {public static void main (String[] args ){
//Step 1
        Layer backgroundLayer = new RedBackgroundLayer();
        System.out.println(backgroundLayer.getDescription() + backgroundLayer.position());
//Step 2
        // 为 RedBackgroundLayer 装饰一个 TriangleLayerDecorator
        Layer multipleLayer = new TriangleLayerDecorator(backgroundLayer);
//Step 3
        // 再装饰一个 RectangleLayerDecorator
        multipleLayer = new RectangleLayerDecorator(multipleLayer);
//Step 4
        // 再装饰一个 RoundLayerDecorator
        multipleLayer = new RoundLayerDecorator(multipleLayer);

        System.out.println(multipleLayer.getDescription() + multipleLayer.position());

    }
}

运行结果:

我是 RedBackgroundLayer-> 我在底层画一个红色的 layer->
我是 RedBackgroundLayer->TriangleLayerDecorator->RectangleLayerDecorator->RoundLayerDecorator-> 我在底层画一个红色的 layer-> 右上角画△-> 左上角画□-> 右下角画○->

运行过程示意图:


最终得到:

正文完
 0

装饰者模式

14次阅读

共计 2085 个字符,预计需要花费 6 分钟才能阅读完成。

设计原则
类应该对扩展开放,对修改关闭。
作用
​ 动态地给一个对象添加一些额外的职责。就增加功能而言,装饰者模式相比生成子类更为灵活,因为生成子类,类的行为只能在编译时静态决定。换句话说,行为不是来自父类就是子类覆盖或者添加后的版本。反之,利用装饰者模式可以把装饰者嵌套使用,可以在运行时实现新的装饰者增加新的行为。如果依赖继承,每当我们需要新行为时还需要修改现有的代码,因为使用继承,类的行为只能在编译时静态决定。
实现要求

接口的一致性。因为装饰者必须必须能取代被装饰者,所以装饰者与被装饰者必须是同一类型,也就是说二者有共同的父类或者实现了共同的接口。“ 类型匹配 ” 可以通过抽象类(或者普通基类如 IO 的实现)、接口来实现。
利用组合和委托将被装饰者 (组件) 添加到装饰者当中。

省略不必要时的抽象的 Decorator 类。当你仅需要添加一个职责时,没有必要定义抽象 Decorator 类, 你尝尝需要处理现存的类层次结构而不是设计一个新系统,这 时你可以把 Decorator 向 Component 转发请求的职责合并到 ConcreteDecorator 中。

保持接口 / 父类的简单性。组件和装饰必须有一个公共的 Component 父类。因此保持这个类的简单性是很重要的;即,它应集中于定义接口而不是存储数据。对数据表示的定义应延迟到子类中,否则 Component 类会变得过于复杂和庞大,因而难以大量使用。赋予赋予 Component 太多的功能也使得,具体的子类有一些它们并不需要的功能的可能性大大增加

装饰者模式与策略模式
​ 改变对象外壳与改变对象内核。

我们可以将 Decorator 看作一个对象的外壳,它可以改变这个对象的行为。
另外一种方法是改变对象的内核。例如,Strategy 模式就是一个用于改变内核的很好的模式。Component 类原本就很庞大时,使用 Decorator 模式代价太高,Strategy 模式相对更好一些。Strategy 模式中,组件将它的一些行为转发给一个独立的策略对象,我们可以替换 strategy 对象,从而改变或扩充组件的功能。例如我们可以将组件绘制边界的功能延迟到一个独立的 Border 对象中,这样就可以支持不同的边界风格。这个 Border 对象是一个 Strategy 对象,它封装了边界绘制策略。我们可以将策略的数目从一个扩充为任意多个,这样产生的效果与对装饰进行递归嵌套是一样的。

装饰者模式的缺点

可能会造成大量的小类
客户代码中可能依赖某种特定类型(比如某个实现了 ” 接口 ” 的具体类中的非接口方法),如果导入的是装饰者又不做考虑,可能会出现问题。即:Decorator 与它的 Component 不一样,Decorator 是一个透明的包装。如果我们从对象标识的观点出发,一个被装饰了的组件与这个组件是有差别的,因此,使用装饰时不应该依赖对象标识,而是应该依赖接口标识。

装饰者模式与代理模式的区别
相同点
​ 这两种模式都描述了怎样为对象提供一定程度上的间接引用,proxy 和 decorator 对象的实现部分都保留了指向另一个对象的引用,它们向这个对象发送请求。像 Decorator 模式一样,Proxy 模式构成一个对象并为用户提供一致的接口。
不同点
​ 但与 Decorator 模式不同的是,Proxy 模式不能动态地添加或分离性质,它也不是为递归组合而设计的。它的目的是,当直接访问一个实体不方便或不符合需要时,为这个实体提供一个替代者,例如,实体在远程设备上,访问受到限制或者实体是持久存储的。
​ 在 Proxy 模式中,实体定义了关键功能,而 Proxy 提供(或拒绝)对它的访问。在 Decorator 模式中,组件仅提供了部分功能,而一个或多个 Decorator 负责完成其他功能。Decorator 模式适用于编译时不能(至少不方便)确定对象的全部功能的情况。这种开放性使递归组合成为 Decorator 模式中一个必不可少的部分。而在 Proxy 模式中则不是这样,因为 Proxy 模式强调一种关系(Proxy 与它的实体之间的关系),这种关系可以静态的表达。模式间的这些差异非常重要,因为它们针对了面向对象设计过程中一些特定的经常发生问题的解决方法。
结合使用
​ 但这并不意味着这些模式不能结合使用。可以设想有一个 proxy-decorator,它可以给 proxy 添加功能,或是一个 decorator-proxy 用来修饰一个远程对象。尽管这种混合可能有用(我们手边还没有现成的例子),但它们可以分割成一些有用的模式。
​ 在我看来 Java 中的动态代理更像是一个 proxy-decorator,也就是代理模式与装饰者模式结合使用。而 Java 中的静态代理才是我们上面说的那种代理。
适用性
以下情况使用 Decorator 模式

在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
处理那些可以撤消的职责。
当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。另一种情况可能是因为类定义被隐藏,或类定义不能用于生成子类,比如 final 修饰的类或者方法。

正文完
 0

装饰者模式?

14次阅读

共计 1130 个字符,预计需要花费 3 分钟才能阅读完成。

什么是装饰者模式?
装饰者模式,其中的重点自然就在“装饰”这两个字上面,在日常生活中很多东西都是需要装饰的,装饰的目的是为了原来的物品更加的好看,或者是加个装饰让物品更加实用,装饰一般都能够为原来的物品添加一些新的功能,而添加的装饰在添加新的功能的时候也不会改变原来的物品。
那么在编程中的装饰其实和生活之中的装饰也是一个道理。所以装饰者模式的一个基本的原则就是在不改变对象自身的基础上,在程序运行期间给对象动态的添加职责,能够使得功能变得可插拔更加的灵活。
模拟传统面向对象语言的装饰者模式
下面通过一个简单的例子来体会一下装饰者模式:
假设现在我们在编写一个飞机大战的游戏,随着等级的上升我们的飞机功能不断的增强,一开始只能发射子弹,二级之后可以发射导弹了,三级之后可以发送原子弹了。
首先实现一个飞机类:
var Plane = function () {}
Plane.prototype.fire = function () {
console.log(‘ 发射普通子弹 ’)
}
接下来添加两个装饰类,分别实现发射原子弹和导弹:
var MissileDecorator = function (plane) {
this.plane = plane
}
MissileDecorator.prototype.fire = function () {
this.plane.fire()
console.log(‘ 发射导弹 ’)
}
var AtomDecorator = function (plane) {
this.plane = plane
}
AtomDecorator.prototype.fire = function () {
this.plane.fire()
console.log(‘ 发射原子弹 ’)
}
导弹类和原子弹类的构造函数都接受参数 plane 对象,并且保留这个参数,在执行他们自身的操作之外还执行 plane 的 fire 方法。
这样子每被装饰一次之后原来的类就新增了一个功能,而且原始的类也没有被改变。
使用:
var plane = new Plane();
plane = new MissileDecorator(plane);
plane = new AtomDecorator(plane);
plane.fire();
// 分别输出:发射普通子弹、发射导弹、发射原子弹
装饰者也是包装容器
从上面的例子可以看到,每经过一次装饰就相当于在原来的类外部包装了一层对象,形成了一条包装链,请求随着这条包装链依次传递到所有的对象,每个对象都会有机会来处理这个请求。
作者简介:李成文,芦苇科技 web 前端开发工程师,擅长网站建设、公众号开发、微信小程序开发、小游戏、公众号开发,专注于前端领域框架、交互设计、图像绘制、数据分析等研究,访问 www.talkmoney.cn 了解更多。

正文完
 0