Bootstrap

设计模式之装饰器模式

装饰器模式(Decorator)

由李建忠老师设计模式课程整理。

装饰器类的应用要点在于解决主体类在多个方向上的扩展功能

GoF《设计模式》中的定义:

动态(组合)地给一个对象增加一些额外的职责。就增加功能而言,Decorator模式比生成子类(继承)更为灵活(消除重复代码 &和减少子类个数)。

假设有一个需求:设计各种类型数据的读写操作。直观的设计是,先设计一个抽象基类,定义数据的读写操作的接口,然后子类继承父类,实现不同数据类型的具体读写操作,代码示例如下:

class Stream
{
public:
    // 读文件流
    virtual char read(int number) = 0;

    // 写文件流
    virtual void write(char data) = 0;

    virtual ~Stream() {}
};

class FileStream : public Stream
{
public:
    virtual char read(int number)
    {
        cout << "文件类型数据的读操作..." << endl;
    }

    virtual void write()
    {
        cout << "文件类型数据的写操作..." << endl;
    }
};

class NetworkStream : public Stream
{
public:
    virtual char read(int number)
    {
        cout << "网络类型数据的读操作..." << endl;
    }

    virtual void write()
    {
        cout << "网络类型数据的写操作..." << endl;
    }
};

class MemoryStream : public Stream 
{
public:
    virtual char read(int number)
    {
        cout << "内存类型数据的读操作..." << endl;
    }

    virtual void write()
    {
        cout << "内存类型数据的写操作..." << endl;
    }
};

现在又增加一个新的需求,要求在读写操作过程中对数据进行加密。为了遵循开放封闭原则,可以创建新的子类,继承实现了读写操作的类,然后重写这些读写操作,实现加密。增加的代码示例如下:

class CryptooFileStream : public FileStream {
public:
    virtual char read(int number)
    {
        // 增加的加密操作
        cout << "对文件数据进行加密..." << endl;
        // 调用父类的读操作
        FileStream::read(number);
    }

    virtual void write()
    {
        // 增加的加密操作
        cout << "对文件数据进行加密..." << endl;
        // 调用父类的写操作
        FileStream::write();
    }
};

class CryptooNetworkStream : public NetworkStream {
public:
    virtual char read(int number)
    {
        // 增加的加密操作
        cout << "对网络数据进行加密..." << endl;
        // 调用父类的读操作
        NetworkStream::read(number);
    }

    virtual void write()
    {
        // 增加的加密操作
        cout << "对网络数据进行加密..." << endl;
        // 调用父类的写操作
        NetworkStream::write();
    }
};

class CryptooMemoryStream : public MemoryStream {
public:
    virtual char read(int number)
    {
        // 增加的加密操作
        cout << "对内存数据进行加密..." << endl;
        // 调用父类的读操作
        MemoryStream::read(number);
    }

    virtual void write()
    {
        // 增加的加密操作
        cout << "对内存数据进行加密..." << endl;
        // 调用父类的写操作
        MemoryStream::write();
    }
};

上述的新需求的实现虽然满足了开放封闭原则,现在看起来没有什么问题,但随着不断的新需求的提出,问题就会迅速暴露出来。例如,现在又提出了一个新的需求,要求在数据读写前,先对其进行缓存操作。可以继续创建新的子类,继承具有读写核心操作功能的类,新增代码示例如下:

class BufferedFileStream : public FileStream {
public:
    virtual char read(int number) 
    {
        // 增加的缓存操作
        cout << "对内存数据进行缓存..." << endl;
        // 调用父类的读操作
        FileStream::read(number);
    }

    virtual void write()
    {
        cout << "对内存数据进行加密..." << endl;
        // 调用父类的写操作
        FileStream::write();
    }
};

class BufferedNetworkStream : public NetworkStream {
    // ...
};

class BufferedMemoryStream : public MemoryStream {
    // ...
};

若又有新的需求出现,或者是各种需求的组合,按照上述不断拓展子类的方法会使得子类的数量快速膨胀,变得不易维护。这种通过继承的方式拓展功能的方式如下图所示:
在这里插入图片描述

现在做一个假设,新需求的提出不会影响数据的读写操作,且这些新需求,例如加密、缓存等操作不会因数据的类型不同,而操作不同。因此,分析上述通过拓展子类而实现新添加功能的方式,其实是在重复很多相同的操作,如下图对比所示,在新增加的子类中,拓展的新功能的操作是相同的,不同的只是不同类型数据的读写操作。
在这里插入图片描述

重复的操作应该把它抽象出来,使其得到复用,进而消除重复,提高代码的可维护性。

可以将上述假设需求的提出总结描述为:在不改变一个类的某个或某些实现的基础上,对这些实现进行拓展;又可说,在不改变类的已有实现的基础上,对这些已有实现新增额外操作。

针对此类情景的问题,可用装饰器模式进行重构。先给出使用装饰器模式对上述代码重构的核心代码:

class CryptoStream : public Stream {
    Stream* stream;

public:
    CryptoStream(Stream *stm) : stream(stm) {}

    virtual char read(int number) {
        // 加密操作
        stream->read(number);
    }

    virtual void write() {
        // 加密操作
        stream->write();
    }
};

如上代码所示,只用一个子类就实现了重构前三个子类实现的功能;对重构的代码进行解释:

  • CrytoStream 继承了 Stream ,不继承 Stream 也没有问题,也能满足需求;继承是为了满足接口规范;

  • 将具体实现的抽象基类 Stream 对象作为 CrytoStream 类的成员,以组合的方式实现拓展功能;

    为什么将抽象基类作为成员?为了实现多态,将编译时依赖转为运行时依赖。这样就可将“是对那种类型的数据进行加密后再读写”由编译时确定转为运行时再确定。

再进一步的重构,重构核心代码如下:

class DecoratorStream : public Stream {
protected:
    Stream *stream;
public:
    DecoratorStream(Stream* stm) : stream(stm) {}

    // 省略 ...
};

class CryptoStream : public DecoratorStream {

public:
    CryptoStream(Stream* stm) : DecoratorStream(stm) {}

    virtual char read(int number) {
        // 加密操作
        stream->read(number);
    }

    virtual void write() {
        // 加密操作
        stream->write();
    }
};

进一步的重构把新增加功能的操作抽象为一个基类,然后实际的新增功能交给子类实现。重构之后类之间的关系就变为如下图所示:
在这里插入图片描述

李建忠老师课程中PPT截图
在这里插入图片描述

总结

  1. 装饰器模式中新增加的功能一定要是在不改变原有实现的前提下,这也是设计模式中要遵循的开放封闭原则。
  2. 通过采用组合而非继承的手法, Decorator模式实现了在运行时 动态扩展对象功能的能力,而且可以根据需要扩展多个功能。避免 了使用继承带来的“灵活性差”和“多子类衍生问题”。
  3. Decorator类在接口上表现为is-a Component的继承关系,即 Decorator类继承了Component类所具有的接口。但在实现上又 表现为has-a Component的组合关系,即Decorator类又使用了 另外一个Component类。
  4. Decorator模式的目的并非解决“多子类衍生的多继承”问题, Decorator模式应用的要点在于解决“主体类在多个方向上的扩展 功能”——是为“装饰”的含义。
;