C++设计模式<六>:Decorator装饰模式

“单一职责”模式

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

典型模式

- Decorator

- Bridge

1.动机

在某些情况下我们可能会“过度地使用继承来扩展对象的功能”,由于继承为类型引入的静态特质,使得这种扩展方式缺乏灵活性;并且随着子类的增多(扩展功能的增多),各种子类的组合(扩展)会导致更多子类的膨胀

那么如何使“对象功能的扩展”能够根据需要动态的实现?同时避免“扩展功能的增多”带来的子类膨胀问题?从而使得任何“功能扩展变化”所导致的影响将为最低。

2.示例

问题描述:设计一组与流相关的类,首先定义一个抽象基类Stream,之后继承各种,如FileStream,NetworkStream,MemoryStream。之后对流进行扩展操作,如加密操作,拷贝操作等。

//业务操作
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();//加密缓存文件流

}

分析:

以上例子是关于:对各种流的操作的,开始只有三个要求(文件流FileStream,网络流NetWorkStream,内存流MemoryStream),然后这三个类都继承于一个抽象类(Stream);之后提出各种需求,需要进行加密操作,缓存操作等等。因此就有了各种扩展情况。下图可以看出其关系。

什么问题呢?可以看出以上的代码存在大量的代码冗余(比如:加密操作都是一样的,无论是针对文件流还是网络流,加密文件流CryptoFileStream的读操作和加密网络流的都操作都是先加密再读),也就是一样的代码。再看看对CryptoFileStream,CryptoNetworkStream,CryptoMemoryStream进行一部分更改(将继承改为组合)的代码(只看读操作)

class CryptoFileStream :{
    FileStream *stream;//更改的地方
public:
    virtual char Read(int number){
        //额外的加密操作...
        stream->Read(number);//更改的地方
    }
};

class CryptoNetworkStream{
    NetworkStream* stream;//更改的地方
public:
    virtual char Read(int number){
        //额外的加密操作...
        stream->Read(number);//更改的地方
    }
};

class CryptoMemoryStream{
     MemoryStream* stream;//更改的地方
public:
    virtual char Read(int number){
        //额外的加密操作...
        stream->Read(number);//更改的地方
    }
};

上面的代码是不是很像,其中各个类添加的成员(FileStream stream,NetworkStream stream,MemoryStream* stream)是不是可以进一步更改为Stream* stream就可以了。更改完的代码如下

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

class CryptoNetworkStream{
    Stream* stream;//new NetworkStream()
public:
    virtual char Read(int number){
        //额外的加密操作...
        stream->Read(number);
    }
};

class CryptoMemoryStream{
     Stream* stream;// new NetWorkStream()
public:
    virtual char Read(int number){
        //额外的加密操作...
        stream->Read(number);
    }
};

编译时一样,运行时不一样绝大多数设计模式的原理,运行时让他变化(用多态来支持其变化)。

这样做完后,你发现这三个类是不是一模一样,那只需要一个类就行了(妙!!!)。这样就消除了重复性,优化的代码如下

class CryptoFileStream :{
    Stream *stream;//...可以有各种各样的流到这里
public:
    virtual char Read(int number){
        //额外的加密操作...
        stream->Read(number);
    }
};

上面代码,但你有没有发现一个问题,CryptoFileStream里的Read凭什么是虚函数,因此必须得继承基类(是为了完善接口规范),因此修改如下

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);//写文件流
        //额外的加密操作...
    }
};

这样CryptoFileStream既有个基类的字段,也继承基类;同样buffer操作的代码优化同上诉方法一样。

这样改完后 ,就可以这样使用

void Process(){
    //运行时装配
    FileStream* s1=new FileStream(); //文件流
    CryptoStream* s2=new CryptoStream(s1);//加密文件流
    BufferedStream* s3=new BufferedStream(s1);//缓冲文件流
    BufferedStream* s4=new BufferedStream(s2);//缓冲加密文件流
}

运行时装配什么意思呢?编译时不存在缓存文件流,什么加密文件流等等,没有那样的类,运行时可以通过组合装配起来满足需求。这就是装饰的含义,装饰是附着在其他对象上

图示如下

以上做法已经很完善了。但是,如果某一个类的子类有同样的字段时,应该往上提。提到哪?

方法一:提到基类。但是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){
        //写内存流
    }

};

//扩展操作,中间类
DecoratorStream: public Stream{ /
protected:
    Stream* stream;//...

    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 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){

    }
    //...
};

总结

  • 以上代码优化过程中,有些类始终没动,但因为该模式的本质上扩展的,就是在谁的基础上再去做,这就是装饰的含义,附着在其他地方上的一个操作。
  • 导致代码不好的原因就是对继承的不良使用,由静态而导致的静态特质,而由组合却可以很好的实现动态(组合优于继承)

当然,面向对象设计原则 里就有条”用组合代替继承”

3.模式定义

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

结构图如下

4.总结

  • 通过采用组合而非继承的手法,Decorator模式实现了在运行时动态扩展对象的功能,而且需要扩展多个功能。避免了使用继承功能带来的“灵活性差”,和多子类衍生功能。
  • Decorator类在接口上变现为is-a Component 的继承关系,即Decorator类继承了Component类所有的接口。但在实现上又变现为has-a Component的组合关系,即Decorator类又使用了另外一个Component类
  • Decortor模式的目的并非解决“多子类衍生的多继承”问题, Decorator模式应用的要点在于解决“主体类在多个方向上的扩展功能”-这就是装饰的含义
时间: 2024-11-05 22:43:12

C++设计模式<六>:Decorator装饰模式的相关文章

c++ 设计模式6 (Decorator 装饰模式)

4. "单一职责"类模式 在软件组件的设计中,如果责任划分的不清晰,使用继承得到的结果往往是随着需求的变化,子类急剧膨胀,同时充斥着重复代码,这时候的关键是划清责任. 典型模式代表: Decorator,Bridge 4.1 Decorator 装饰模式 代码示例:不同的流操作(文件流,网络流,内存流)及其扩展功能(加密,缓冲)等的实现 实现代码1: 类图结构示意(大量使用继承) 数据规模: 假设有n种文件,m种功能操作.该实现方法有(1 + n + n * m! / 2) 数量级的子

设计模式09: Decorator 装饰模式(结构型模式)

Decorator 装饰模式(结构型模式) 子类复子类,子类何其多加入我们需要为游戏中开发一种坦克,除了不同型号的坦克外,我们还希望在不同场合中为其增加以下一种多种功能:比如红外线夜视功能,比如水路两栖功能,比如卫星定位功能等等. 问题代码: /// <summary> /// 抽象坦克 /// </summary> public abstract class Tank { public abstract void Shot(); public abstract void Run(

Android设计模式--装饰模式

1.定义: Attach additional responsibilities to an object dynamically keeping the same interface. Decoators provide a flexible alternative to subclassing for extending functionality. 在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能.它是通过创建一个包装对象,也就是装饰来包裹真实的对象. 2.装饰模式,本质就是

设计模式学习之装饰模式:IO流的装饰器

IO流的装饰器 题目分析:通过对java的io系列类分析得知,java的io流使用了设计模式中的装饰模式,以动态的给一个对象增加职责,能够更加灵活的增加功能.通过看io的源代码得知FilterOutputStream类继承了OutputStream类并拥有父类的一个对象,它和父类具有组合聚合的关系.因此要实现我们自己的加密类只需扩展FilterOutputStream类重写它的wite方法即可 UML图: 源代码: package com.cmc import java.io.FilterOut

设计模式之三:装饰模式(Decorator)

装饰模式: 动态地给对象添加一些相关的职责.装饰模式相比与添加子类提供了一种更加灵活的方式. UML图如下所示: 感觉上图中关键的有这几点: Decorator与Component的聚合关系(即Decorator中存在一个Component类型的引用),由于这个聚合关系的存在,Decorator可以通过一个Component的引用调用Component的接口 Decorator与Component的继承关系,这个关系不是很容易理解.但是在这里联想到继承的里氏代换原则(父类出现的地方都可以替换成子

设计模式之笔记--装饰模式(Decorator)

装饰模式(Decorator) 定义 装饰模式(Decorator),动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更为灵活. 类图 描述 Component:被装饰者和装饰者共有的基类: ConcreteComponent:被装饰者的具体类: Decorator:装饰类,包含一个Component实例: ConcreteDecorator:具体的装饰类,其构造函数里有一个Component实例,方法可以扩展. 应用场景 下面条的时候,可以在面条里放入西红柿.鸡蛋等食材,

设计模式C++学习笔记之十三(Decorator装饰模式)

装饰模式,动态地给一个对象添加一些额外的职责.就增加功能来说,Decorator模式相比生成子类更为灵活. 13.1.解释 main(),老爸 ISchoolReport,成绩单接口 CFourthGradeSchoolReport,四年级成绩单 ReportDecorator,成绩单装饰器基类 HighScoreDecorator,最高分装饰器 SortDecorator,班级排名装饰器 说明:对"四年级成绩单"进行装饰,ReportDecorator必然有一个private变量指向

大话设计模式第六章---装饰模式PHP实现

1 <?php 2 abstract class Component { 3 abstract public function operation(); 4 } 5 6 class Concrete_component extends Component { 7 public function operation() { 8 echo "具体对象的操作<br/>"; 9 } 10 } 11 12 abstract class Decorator extends Com

设计模式实现C++ --装饰模式Decorator Pattern

定义:动态地将责任附加到对象上.若要扩展功能,装饰者提供了比继承更有弹性的替代方案. 类图: Component:定义一个对接接口,可以给这些对象动态的添加职责: ConcreteComponent:定义一个具体的对象,也可以给对象添加一些职责: Decorator:装饰抽象类,继承了Component,从外类来扩展Component类的功能,对于Component来说无需知道Decorator的存在: ConcreteDecorator:具体的装饰对象,起到给Component添加职责的功能: