0. 简介

很多时候我们在接手代码的时候会发现很多时候我们需要在原本基础的类中加入一些功能,或者要调用多个类来组合成一个新类来工作。这时候我们需要使用装饰或者适配器模式来对整个结构去聚合或者多重继承出自己想要的类型。装饰模式是一种结构型设计模式,其可以动态的为一个类增加职责(相对于继承)。
在这里插入图片描述

装饰模式解决方案

1装饰模式

装饰设计模式的结构

  1. 部件 (Component) 声明封装器和被封装对象的公用接口。
  2. 具体部件 (Concrete Component) 类是被封装对象所属的类。 它定义了基础行为, 但装饰类可以改变这些行为。
  3. 基础装饰 (Base Decorator) 类拥有一个指向被封装对象的引用成员变量。 该变量的类型应当被声明为通用部件接口, 这样它就可以引用具体的部件和装饰。 装饰基类会将所有操作委派给被封装的对象。
  4. 具体装饰类 (Concrete Decorators) 定义了可动态添加到部件的额外行为。 具体装饰类会重写装饰基类的方法, 并在调用父类方法之前或之后进行额外的行为。
  5. 客户端 (Client) 可以使用多层装饰来封装部件, 只要它能使用通用接口与所有对象互动即可。

2. 装饰模式示例

装饰模式和生成器模式类似,都没有一个合适的template模板类来便捷的导入,这里我们可以看一下这篇文章中的代码。来感受一下装饰模式的魅力。

#include "iostream"

using namespace std;

// 部件
class Component_CS {
 public:
  virtual ~Component_CS() {}
  //const表示this是一个指向常量的指针,=0表示这个成员函数是纯虚函数
  // https://blog.csdn.net/weixin_45525272/article/details/105840562
  virtual void fire() const = 0;
}; 

// 具体部件
class ConcreteComponent_CS
{
public :
    ConcreteComponent_CS(){}
    virtual ~ConcreteComponent_CS(){}
    virtual void fire() const
    {
        cout<<"反恐精英:"<<endl;
    }
    // void fire() const override
    // {
    //     cout<<"反恐精英:"<<endl;
    // }
    // 如果没有实例化,就需要加上virtual or override
    // void fire() const
    // {
    //     cout<<"反恐精英:"<<endl;
    // }
};
//基础装饰
class Weapon : public Component_CS
{
protected:
    Component_CS * person;
public:
    Weapon(){}
    virtual ~Weapon(){}
    //装饰函数
    void Decorate(Component_CS * p)
    {
        person = p;
    }
    virtual void fire()
    {
        person->fire();
    }

};
//具体装饰
class Ak47 : public Weapon
{
public :
    virtual void fire()
    {
        Weapon::fire();
        cout<<"AK47射击"<<endl;
    }
};

//具体装饰
class Pistol : public Weapon
{
public :
    virtual void fire()
    {
        Weapon::fire();
        cout<<"手枪射击"<<endl;
    }
};

//具体装饰
class Dagger : public Weapon
{
public :
    virtual void fire()
    {
        Weapon::fire();
        cout<<"捅你"<<endl;
    }
};
main()
{
    Component_CS * person = new ConcreteComponent_CS();
    Weapon * ak = new Ak47();
    Weapon * pistol = new Pistol();
    Weapon * dagger = new Dagger();

    ak->Decorate(person);
    pistol->Decorate(ak);
    dagger->Decorate(pistol);

    dagger->fire();
}

img
我们可以看到在main函数中,我们可以通过装饰类的嵌套来将一个函数开发出更多的功能且不会影响前面类的输出。这对于我们去无需修改代码的情况下即可使用对象, 且希望在运行时为对象新增额外的行为, 可以使用装饰模式。同时如果用继承来扩展对象行为的方案难以实现或者根本不可行, 你可以使用装饰模式。

3. 装饰器的优缺点

 一般给对象添加新的功能可能会通过继承的方式来解决,但是呢,继承会带来的问题就是,子类越来越多且会越来越臃肿。 我们知道在七大设计原则中有一条就是【组合/聚合复用原则】。在拥有很多组合方式的时候,我们应该优先使用组合而不是继承,装饰器模式在这个原则表现的就很强烈

下一章我们讲的适配器模式可以对已有对象的接口进行修改, 装饰模式则能在不改变对象接口的前提下强化对象功能。 此外, 装饰还支持递归组合, 适配器则无法实现。

在这里插入图片描述

4. 七大设计原则

4.1 开闭原则

开闭原则(Open Closed Principle,OCP)的定义是:一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。模块应尽量在不修改原(是”原”,指原来的代码)代码的情况下进行扩展。代表:装饰者模式

4.2 单一职责原则

单一职责原则(Single Responsibility Principle, SRP)的定义是:指一个类或者模块应该有且只有一个改变的原因。如果一个类承担的职责过多,就等于把这些职责耦合在一起了。一个职责的变化可能会削弱或者抑制这个类完成其他职责的能力。代表:迭代器模式

4.3 里氏代换原则

里氏代换原则(Liskov Substitution Principle,LSP)的定义是:所有引用基类的地方必须能透明地使用其子类的对象,也可以简单理解为任何基类可以出现的地方,子类一定可以出现。里氏代换原则是实现开闭原则的基础,它告诉我们在设计程序的时候进可能使用基类进行对象的定义和引用,在运行时再决定基类的具体子类型。

4.4 依赖倒转原则

依赖倒转原则(Dependency Inversion Principle,DIP)的定义:程序要依赖于抽象接口,不要依赖于具体实现。 简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。代表:工厂模式

4.5 接口隔离原则

接口隔离原则(Interface Segregation Principle,ISP)的定义是客户端不应该依赖它不需要的接口,类间的依赖关系应该建立在最小的接口上。简单来说就是建立单一的接口,不要建立臃肿庞大的接口。也就是接口尽量细化,同时接口中的方法尽量少。接口设计是有限度的,接口的设计粒度越小,系统越灵活,但是值得注意不能过小,否则变成”字节码编程”。

4.6 合成/聚合复用原则

合成/聚合复用原则(Composite/Aggregate Reuse Principle,CARP)一般也叫合成复用原则(Composite Reuse Principle, CRP),定义是:尽量使用合成/聚合,而不是通过继承达到复用的目的代表:策略模式

4.7 迪米特法则

迪米特法则(Law of Demeter,LOD),有时候也叫做最少知识原则(Least Knowledge Principle,LKP),它的定义是:一个软件实体应当尽可能少地与其他实体发生相互作用。每一个软件单位对其他的单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位。尽量少发布public的变量和方法,一旦公开的属性和方法越多,修改的时候影响的范围越大。代表:外观模式 中介者模式

5. 参考链接

https://www.cnblogs.com/hebaichuanyeah/p/5612028.html

https://refactoringguru.cn/design-patterns/decorator

https://blog.csdn.net/leacock1991/article/details/112056643

https://blog.csdn.net/Void_leng/article/details/115660440

https://www.jianshu.com/p/3a0e9fba3a41