将4年前的设计模式笔记再看一遍(4),Decorator

一、模式定义

分组到单一职责组,这个模式的讲解过程中提到,这是组合的应用。继承太多导致子类膨胀,重构的方向是划清责任,其后组合在一起。

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

1、原有代码

当类的继承过多之后,代码好臃肿。按照下面的原始版本设计,是有问题的,最终类的个数可能是1 + n + n * (m! / 2) 。并且,子类中的许多代码是重复的。

arduino 复制代码
class Stream {
public:
    virtual char read(int number) = 0;
    virtual void seek(int position) = 0;
    virtual void write(char data) = 0;
    
    virtual ~Stream() {}
};

// 主体部分
class FileStream : public Stream {
public:
    virtual char read(int number) {
        // 读文件流
    }
    
    virtual void seek(int position) {
        // 定位文件流
    }
    
    virtual void write(char data) {
        // 写文件流
    }
};


class NetworkStream : public Stream {
public:
    virtual char read(int number) {
        // 读网络流
    }
    
    virtual void seek(int position) {
        // 定位网络流
    }
    
    virtual void write(char data) {
        // 写网络流
    }
};


class MemoryStream : public Stream {
public:
    virtual char read(int number) {
        // 读内存流
    }
    
    virtual void seek(int position) {
        // 定位内存流
    }
    
    virtual void write(char data) {
        // 写内存流
    }
};

// 扩展操作
// 需要对每一种流做加密操作,加密操作时一样的,只是流不一样
// 此处基于所有的流继承出新的字类,有许多重复的加密代码
class CryptoFileStream : public FileStream {
public:
    virtual char read(int number) {
        // 额外的加密操作...
        FileStream::read(number);
    }
        
    virtual void seek(int position) {
        // 额外的加密操作...
        FileStream::seek(position);
        // 额外的加密操作...
    }
    
    virtual void write(char data) {
        // 额外的加密操作...
        FileStream::write(data);
        // 额外的加密操作...
    }
};

class CryptoNetworkStream : public NetworkStream {
public:
    virtual char read(int number) {
        // 额外的加密操作...
        NetworkStream::read(number);
    }
        
    virtual void seek(int position) {
        // 额外的加密操作...
        NetworkStream::seek(position);
        // 额外的加密操作...
    }
    
    virtual void write(char data) {
        // 额外的加密操作...
        NetworkStream::write(data);
        // 额外的加密操作...
    }
};

// 当复制到这个类的时候,就已经发现,这必须要优化一下写法了,不然一直搞下去,那?
class CryptoMemoryStream : public MemoryStream {
public:
    virtual char read(int number) {
        // 额外的加密操作...
        MemoryStream::read(number);
    }
        
    virtual void seek(int position) {
        // 额外的加密操作...
        MemoryStream::seek(position);
        // 额外的加密操作...
    }
    
    virtual void write(char data) {
        // 额外的加密操作...
        MemoryStream::write(data);
        // 额外的加密操作...
    }
};

// 需要对流进行缓冲,缓冲和加密一样理解,都会有重复内容
class BufferedFileStream : public FileStream {
    // ...
};

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

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

// 加密缓冲同时有,当然是可以继承CryptoFileStream或者BufferedFileStream的
class CryptoBufferedFileStream : public FileStream {
public:
    virtual void read(int number) {
        // 额外的加密操作...
        // 额外的缓冲操作...
        FileStream::read(number);
    }
    
    virtual void seek(int position) {
        // 额外的加密操作...
        // 额外的缓冲操作...
        FileStream::seek(position);
        // 额外的加密操作...
        // 额外的缓冲操作...
    }
    
    virtual void write(char data) {
        // 额外的加密操作...
        // 额外的缓冲操作...
        FileStream::write(data);
        // 额外的加密操作...
        // 额外的缓冲操作...
    }
};

此版本有两个问题:一是类的数量太多,二是重复代码太多。

2、重构版本

先将3个扩展基础类改为1个。

每一个Ctypto*Stream,都调用的基类方法,3个Ctypto类,调用3个不同的基类方法,它们的3个基类继承于同一个基类Stream,于是可以使用多态,在代码上,把调用基类,改为对一个Stream*成员的调用。

既然将对基类FileStreamread调用改为类成员stream调用,那么3个添加了加密操作的类,代码上是一样的,具体差异则体现在stream多态调用上面,所以3个类可以合为一个类,直接继承Stream

其实继承Stream和有一个stream的成员,是互不干扰的两回事。

继承Stream,定义了子类的接口规范(这是老师的原话)。
stream成员,多态的运用。设计模式真谛:编译时一样,使用多态,在运行时弄出不一样。其实就是为CryptoStream的stream赋值的时候,直接new FileStream。

arduino 复制代码
class CryptoStream : public Stream {
    // 运行时,变为不同的FileStream、NetworkStream或者MemoryStream
    Stream* stream;
    
public:
    CtyptoStream(Stream *stm) : stream(stm) {}

    virtual char read(int number) {
        // 额外的加密操作...
        stream->read(number);
    }
        
    virtual void seek(int position) {
        // 额外的加密操作...
        stream->seek(position);
        // 额外的加密操作...
    }
    
    virtual void write(char data) {
        // 额外的加密操作...
        stream->write(data);
        // 额外的加密操作...
    }
};

// 同理可以有BufferedStream设定
class BufferedStream : public Stream {
    Stream* stream;
public:
    BufferedStream(Stream *stm) : stream(stm) {}
    // ...
};

3、最终版本

当有以上的写法之后,还可以稍微重构下,如果两个子类都有相同的共同变量,那么应该将这些共有变量提取到父类中,是为装饰模式。

arduino 复制代码
DecoratorStream : public Stream {
protected:
    Stream* stream;
    DecoratorStream(Stream *stm) : stream(stm) {}
};

class CtyptoStream : public DecoratorStream {
public:
    CtyptoStream(Stream *stm) : DecoratorStream(stm) {}
    // ...
};

class BufferedStream : public DecoratorStream {
public:
    BufferedStream(Stream *stm) : DecoratorStream(stm) {}
    // ...
};

4、两种不同的调用方式

scss 复制代码
void process() {
    // 编译时装配, 未改进版本的使用
    CryptoFileStream* fs1 = new CryptoFileStream();
    BufferedFileStream* fs2 = new BufferedFileStream();
    CryptoBufferedFileStream* fs3 = new CryptoBufferedFileStream();
    
    // 运行时装配,改进版本的使用
    FileStream* s1 = new FileStream();
    CryptoStream* s2 = new CryptoStream(s1);
    BufferedStream* s3 = new BufferedStream(s1);
    
    // 即加密,又缓冲. 这里的执行,以调用read举例
    // s4->read(n), 首先会执行一段BufferedStream的'额外缓冲操作'
    // 然后执行s2的加密操作
    // 这里的组合,归根结底,就是各种函数的组合?只是在代码上美观很多?
    BufferedStream* s4 = new BufferedStream(s2);
}

二、项目应用

项目中使用较少。

有想一想Python的装饰器和这个模式的关系。

有一点点关系:它们都是为了在不改变原有代码基础上做些新的事情。

但区别大大的,Python的装饰器是语言语法层面上的内容,而装饰模式是设计层面的,与语言无关。

三、其他杂谈

1、感觉这个模式对现在的我来说,已经有深度了,需要思考,且项目中似乎没用到。但是再次被强调,模式都是通过代码重构得出的。

2、其实我现在(2020-7-9)有点疑惑的是,最终的版本真的能够达到目的么?心中知道是的,因为是一步一步跟着重构过来的。但重构前后的变化,是真的大啊!重新理一遍之后,理解多了一些,但是没有实际的使用经验,没有以前3个模式的融会贯通。

3、四年后再看本模式,相较四年前变化并不多,因为工作中该模式使用的依然很少。不过我知道的是,重复代码太多,是肯定不行的,当看见重复代码很多时,就需要重构了。

另外,此篇博客讲得很好:refactoringguru.cn/design-patt...

相关推荐
转世成为计算机大神1 小时前
易考八股文之Java中的设计模式?
java·开发语言·设计模式
小乖兽技术2 小时前
23种设计模式速记法
设计模式
小白不太白9504 小时前
设计模式之 外观模式
microsoft·设计模式·外观模式
小白不太白9504 小时前
设计模式之 原型模式
设计模式·原型模式
澄澈i4 小时前
设计模式学习[8]---原型模式
学习·设计模式·原型模式
小白不太白95011 小时前
设计模式之建造者模式
java·设计模式·建造者模式
菜菜-plus13 小时前
java 设计模式 模板方法模式
java·设计模式·模板方法模式
萨达大13 小时前
23种设计模式-模板方法(Template Method)设计模式
java·c++·设计模式·软考·模板方法模式·软件设计师·行为型设计模式
机器视觉知识推荐、就业指导14 小时前
C++设计模式:原型模式(Prototype)
c++·设计模式·原型模式
阳光开朗_大男孩儿14 小时前
组合模式和适配器模式的区别
设计模式·组合模式·适配器模式