Rust·设计模式·装饰器

 

对继承的不良应用会导致爆炸的子类数量 如何通过组合/聚合的方式扩展一个类的功能

  • 对继承的不良应用会导致爆炸的子类数量
  • 如何通过组合/聚合的方式扩展一个类的功能

《cpp设计模式》源码

在软件组件的设计中,如果责任划分的不清晰,使用继承得到的结果往往是随着需求的变化,子类急剧膨胀,同时充斥着重复代码, 这时候的关键是划清责任

  • 典型模式
    • Decorator
    • Bridge

缘起 Motivation

  • 在某些情况下我们可能会“过度地使用继承来扩展对象的功能”
    • 由于继承为类型引入的静态特质,使得这种扩展方式缺乏灵活性
      • 静态特质(继承实现)

        NetworkStream::Read(number);
        FileStram::Read(number);
        
      • 动态特质(组合/聚合实现)

        stream->Read(number);
        
    • 并且随着子类的增多(扩展功能的增多)
    • 各种子类的组合(扩展功能的组合)会导致更多子类的膨胀
  • 如何使“对象功能的扩展”能够根据需要来动态地实现?
    • 同时避免“扩展功能的增多”带来的子类膨胀问题?
    • 从而使得任何“功能 扩展变化”所导致的影响将为最低?

初级设计

  • 一个IO流操作的功能
    • 流的需求
      • 文件流
      • 网络流
      • 内存流
    • 操作的需求
      • 加密
      • 缓存

设计类图

这种继承方式,规模非常大

一个文件流的基类

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(byte data){
        //额外的加密操作...
        FileStream::Write(data);//写文件流
        //额外的加密操作...
    }
};
  • 加密操作以子类的方式继承对应的流父类

    // 加密文件流
    class CryptoFileStream :public FileStream{ ...}
    class CryptoNetworkStream :public NetworkStream {...}
    class CryptoMemoryStream :public MemoryStream{...}
    
    // 缓存文件流
    class BufferedFileStream : public FileStream{ ... };
    class BufferedNetworkStream : public NetworkStream{ //...  };
    class BufferedMemoryStream : public MemoryStream{ //...  }
    
    // 加密缓存文件流
    class CryptoBufferedFileStream :public FileStream{...}
    
  • 但是内部每个加密操作代码都是一样的

    virtual void Seek(int position){
        //额外的加密操作...
        Stream::Seek(position);//定位流
        //额外的加密操作...
    }
    
    // 加密又缓存
    virtual void Seek(int position){
        //额外的加密操作...
        //额外的缓冲操作...
        Stream::Seek(position);//定位流
        //额外的加密操作...
        //额外的缓冲操作...
    }
    

重构设计

继承变成组合

  • 继承

    class CryptoFileStream :public FileStream{
    public:
        virtual char Read(int number){
            //额外的加密操作...
            FileStream::Read(number);//读文件流
        }
    };
    
  • 组合

    class CryptoFileStream {
      FileStream *stream
    public:
        virtual char Read(int number){
            //额外的加密操作...
            stream->Read(number);//读文件流
        }
    };
    

组合再通过抽象重构

class CryptoFileStream {
  Stream *stream; // = new FileStream(); [不同]
public:
    virtual char Read(int number){
        //额外的加密操作...
        stream->Read(number);//读文件流
    }
};

class NetworkStream {
  Stream *stream; // = new NetworkStream(); [不同]
public:
    virtual char Read(int number){
        //额外的加密操作...
        stream->Read(number);//读文件流
    }
};
  • 通过 stream = new FileStream() 得到具体的stream对象
  • 把编译期的对象编程运行时的对象
  • 设计模式的真谛
    • 编译时复用
    • 变化(需求的变化)都放到运行时
    • 通过多态的方式支持变化
  • 上面编译期都是 Stream *stream
    • 在运行时用new xxxStream()来产生变化的对象

那么最后

class CryptoFileStream :public FileStream{ ...}
class CryptoNetworkStream :public NetworkStream {...}
class CryptoMemoryStream :public MemoryStream{...}

就可以重构成

class CryptoFileStream -> class CryptoStream

class CryptoStream: public Stream {
    Stream* stream; //... 运行时的对象多态
public:
    CryptoStream(Stream* stm):stream(stm){ }

    virtual char Read(int number){
        //额外的加密操作...
        stream->Read(number);//读文件流
    }

    virtual void Seek(int position){
        //额外的加密操作...
        stream::Seek(position);//定位文件流
        //额外的加密操作...
    }

    virtual void Write(byte data){
        //额外的加密操作...
        stream::Write(data);//写文件流
        //额外的加密操作...
    }
};
  • 这里继承public Stream是为了保证虚函数的接口标准
    • Stream定义了接口规范

重构优化

class CryptoStream: public Stream {
    Stream* stream;//...
    ...
}

class BufferedStream : public Stream{
    Stream* stream;//...
    ...
}
  • 如果子类都有同一个字段,可以把这个字段上提
    • 可以把 Stream 的子类的 Stream *stream 上提到基类Stream中

      class Stream {
        Stream *stream;
      }
      
      class CrytoSteam: public Stream {}
      
  • 但FileStream这个子类不需要Stream这个字段

    class FileStream: public Stream{
      public:
          virtual char Read(int number){ //读文件流 }
    }
    

    所以Stream *stream字段应该提升到一个中间类

  • 中间类 Decorator

    class DecoratorStream: public Stream{
    protected:
        Stream* stream;//...
        DecoratorStream(Stream * stm):stream(stm){ }
    };
    
  • Stream* stream字段的子类继承中间类Decorator

    class CryptoStream: public DecoratorStream {
    public:
        CryptoStream(Stream* stm):DecoratorStream(stm){ }
          
        virtual char Read(int number){
            //额外的加密操作...
            stream->Read(number);//读文件流
        }
        virtual void Seek(int position){
            //额外的加密操作...
            stream::Seek(position);//定位文件流
            //额外的加密操作...
        }
        virtual void Write(byte data){
            //额外的加密操作...
            stream::Write(data);//写文件流
            //额外的加密操作...
        }
    };
    
    class BufferedStream : public DecoratorStream{
        Stream* stream;//...
    public:
        BufferedStream(Stream* stm):DecoratorStream(stm){ }
        ...
    }
    

    装饰:在其他类的基础上再去做新的功能

重构后的类图

对比之前对继承不良使用造成的子类爆炸

修改使用方式

  • 重构前编译时装配

    void Process(){
        CryptoFileStream *fs1 = new CryptoFileStream();
        BufferedFileStream *fs2 = new BufferedFileStream();
        CryptoBufferedFileStream *fs3 =new CryptoBufferedFileStream();
    }
    
  • 重构后运行时装配

    void Process(){
        FileStream* s1=new FileStream();
    
        // crypto + filestream
        CryptoStream* s2=new CryptoStream(s1);
        // buffer + filestream
        BufferedStream* s3=new BufferedStream(s1);
        // buffer + crypto + filestream
        BufferedStream* s4=new BufferedStream(s2);
    }
    

模式定义

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

装饰器结构

要点总结

  • 通过采用组合而非继承的手法
    • Decorator模式实现了在运行时动态扩展对象功能的能力
    • 而且可以根据需要扩展多个功能
    • 避免了使用继承带来的“灵活性差”和“多子类衍生问题”
  • Decorator类在接口上表现为is-a Component的继承关系
    • 即Decorator类继承了Component类所具有的接口
    • 但在实现上又表现为has-a Component的组合关系
    • 即Decorator类又使用了另外一个Component类
    • 说明

      class DecoratorStream: public Stream{
      protected:
          Stream* stream;//...
          DecoratorStream(Stream * stm):stream(stm){ }
      };
      
      • Decorator继承了Stream
        • 为了完善接口的规范
      • 但内部又聚合了父类Stream
        • 为了动态的运行时装配对象
        • 为了将来支持动态实例化
  • Decorator模式的目的并非解决“多子类衍生的多继承”问题
    • Decorator模式应用的要点在于解决“主体类在多个方向上的扩展功能”——是为“装饰”的含义
      • FileStream、NetworkStream是一个变化的方向
        • 主体操作
      • CryptoStream和FileStream不是一个变化的方向
        • 是扩展操作

附录

初始设计

//业务操作
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(byte 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(byte 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(byte data){
        //额外的加密操作...
        MemoryStream::Write(data);//写内存流
        //额外的加密操作...
    }
};

class BufferedFileStream : public FileStream{
    //...
};

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

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

class CryptoBufferedFileStream :public FileStream{
public:
    virtual char Read(int number){
        //额外的加密操作...
        //额外的缓冲操作...
        FileStream::Read(number);//读文件流
    }
    virtual void Seek(int position){
        //额外的加密操作...
        //额外的缓冲操作...
        FileStream::Seek(position);//定位文件流
        //额外的加密操作...
        //额外的缓冲操作...
    }
    virtual void Write(byte data){
        //额外的加密操作...
        //额外的缓冲操作...
        FileStream::Write(data);//写文件流
        //额外的加密操作...
        //额外的缓冲操作...
    }
};

void Process(){
        //编译时装配
    CryptoFileStream *fs1 = new CryptoFileStream();
    BufferedFileStream *fs2 = new BufferedFileStream();
    CryptoBufferedFileStream *fs3 =new CryptoBufferedFileStream();
}

重构

//业务操作
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 CryptoStream: public Stream {
    Stream* stream;//...
public:
    CryptoStream(Stream* stm):stream(stm){ }
    virtual char Read(int number){
        //额外的加密操作...
        stream->Read(number);//读文件流
    }
    virtual void Seek(int position){
        //额外的加密操作...
        stream::Seek(position);//定位文件流
        //额外的加密操作...
    }
    virtual void Write(byte data){
        //额外的加密操作...
        stream::Write(data);//写文件流
        //额外的加密操作...
    }
};



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

void Process(){
    //运行时装配
    FileStream* s1=new FileStream();

    CryptoStream* s2=new CryptoStream(s1);
    BufferedStream* s3=new BufferedStream(s1);
    BufferedStream* s4=new BufferedStream(s2);
}

Rust 实现

实现功能

  • 实现一个穿衣功能,给工程师穿上各种衣服
  • 装饰器模式的理解
    • 装饰器继承了被装饰的类,主要是为了规范它要装饰的类的接口,以便像使用原来类的方式来使用装饰后的实例
    • 装饰器又组合了被装饰的类,是为了利用多态特性,它可以调用所有继承此类的各种实例
    • 然后,装饰器在它的调用接口中,先调用被装饰类的函数接口,然后再调用想加上的装饰功能

结构

具体实现

trait IPerson {
    fn 穿着(&self);
}

struct 工程师 {
    技能: String,
    名字: String,
}
impl 工程师 {
    fn new(技能: String, 名字: String) -> Self {
        Self { 技能, 名字 }
    }
}
impl IPerson for 工程师 {
    fn 穿着(&self) {
        println!("[工程师] {}, 技能: {},着装:", self.名字, self.技能);
    }
}

struct 老师 {
    头衔: String,
    名字: String,
}
impl 老师 {
    fn new(头衔: String, 名字: String) -> Self {
        Self { 头衔, 名字 }
    }
}
impl IPerson for 老师 {
    fn 穿着(&self) {
        println!("[老师] {}, 头衔: {},着装:", self.名字, self.头衔);
    }
}

trait 服装装饰器 {
    fn 穿好服饰(&self);
}

struct 休闲裤装饰器 {
    : Box<dyn IPerson>,
}
impl 休闲裤装饰器 {
    fn new(: Box<dyn IPerson>) -> Self {
        Self {  }
    }
}
impl IPerson for 休闲裤装饰器 {
    fn 穿着(&self) {
        self.人.穿着();
        self.穿好服饰();
    }
}
impl 服装装饰器 for 休闲裤装饰器 {
    fn 穿好服饰(&self) {
        println!("[着装] 一条卡其色休闲裤");
    }
}

struct 腰带装饰器 {
    : Box<dyn IPerson>,
}
impl 腰带装饰器 {
    fn new(: Box<dyn IPerson>) -> Self {
        Self {  }
    }
}
impl IPerson for 腰带装饰器 {
    fn 穿着(&self) {
        self.人.穿着();
        self.穿好服饰();
    }
}
impl 服装装饰器 for 腰带装饰器 {
    fn 穿好服饰(&self) {
        println!("[着装] 一条银色针扣头的黑色腰带");
    }
}

struct 皮鞋装饰器 {
    : Box<dyn IPerson>,
}
impl 皮鞋装饰器 {
    fn new(: Box<dyn IPerson>) -> Self {
        Self {  }
    }
}
impl IPerson for 皮鞋装饰器 {
    fn 穿着(&self) {
        self.人.穿着();
        self.穿好服饰();
    }
}
impl 服装装饰器 for 皮鞋装饰器 {
    fn 穿好服饰(&self) {
        println!("[着装] 一双深色休闲皮鞋");
    }
}

struct 针织毛衣装饰器 {
    : Box<dyn IPerson>,
}
impl 针织毛衣装饰器 {
    fn new(: Box<dyn IPerson>) -> Self {
        Self {  }
    }
}
impl IPerson for 针织毛衣装饰器 {
    fn 穿着(&self) {
        self.人.穿着();
        self.穿好服饰();
    }
}
impl 服装装饰器 for 针织毛衣装饰器 {
    fn 穿好服饰(&self) {
        println!("[着装] 一件紫红色针织毛衣");
    }
}

struct 眼镜装饰器 {
    : Box<dyn IPerson>,
}
impl 眼镜装饰器 {
    fn new(: Box<dyn IPerson>) -> Self {
        Self {  }
    }
}
impl IPerson for 眼镜装饰器 {
    fn 穿着(&self) {
        self.人.穿着();
        self.穿好服饰();
    }
}
impl 服装装饰器 for 眼镜装饰器 {
    fn 穿好服饰(&self) {
        println!("[着装] 一幅金属圆形眼镜");
    }
}

fn main() {
    let 刘璟 = 工程师::new("前端工程师".to_string(), "刘璟".to_string());
    let 刘璟 = 休闲裤装饰器::new(Box::new(刘璟));
    let 刘璟 = 皮鞋装饰器::new(Box::new(刘璟));
    let 刘璟 = 腰带装饰器::new(Box::new(刘璟));
    let 刘璟 = 针织毛衣装饰器::new(Box::new(刘璟));
    let 刘璟 = 眼镜装饰器::new(Box::new(刘璟));
    刘璟.穿着();
    println!();

    let 唐维 = 老师::new("教授".to_string(), "唐维".to_string());
    let 唐维 = 针织毛衣装饰器::new(Box::new(唐维));
    let 唐维 = 眼镜装饰器::new(Box::new(唐维));
    唐维.穿着();
}

执行结果:

[工程师] 刘璟, 技能: 前端工程师,着装:
[着装] 一条卡其色休闲裤
[着装] 一双深色休闲皮鞋
[着装] 一条银色针扣头的黑色腰带
[着装] 一件紫红色针织毛衣
[着装] 一幅金属圆形眼镜

[老师] 唐维, 头衔: 教授,着装:
[着装] 一件紫红色针织毛衣
[着装] 一幅金属圆形眼镜