设计模式---单一职责模式之装饰模式(Decorator)

前提:"单一职责"模式

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

典型模式(表现最为突出)

装饰模式Decorator
桥接模式Bridge

一:装饰模式

(一)概念

装饰模式又叫做包装模式。通过一种对客户端透明的方式来扩展对象的功能,是继承关系的一个替换方案。
装饰模式就是把要添加的附加功能分别放在单独的类中,并让这个类包含它要装饰的对象,当需要执行时,客户端就可以有选择的,顺序的使用装饰功能包装对象

(二)动机

在某些情况下我们可能会“过度的使用继承来扩展对象的功能”,由于继承为类型引入的静态特质,使得这种扩展方式缺乏灵活性;并且随着子类的增多(扩展功能的增多),各种子类的组合(扩展功能的组合)会导致更多的子类膨胀。即我们需要找到一种方式,实现功能可通过非继承方式扩展,但是接口不发生变化的类。
如何使“对象功能的扩展”能够根据需要来动态地实现?同时避免“扩展功能的增多”带来的子类膨胀问题?从而使得任何“功能扩展变化”所导致的影响将为最低?

(三)原代码讲解(流操作)

文件流,内存流,网络流,有对流进行加密,缓存等操作
//业务操作
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();

}

出现的问题:

需要的类的个数是:
            1+n+n*(m+m-1+...+2+1)      
其中n是我们流的个数,m是我们的操作类型个数。
我们上面的n=3,m=2所以需要的类是13个

问题的原因

除了其中的具体操作,例如:文件读取,网络读取等不同,我们发现所有的加密和缓存操作都是一样的方法,出现大量代码冗余,

(四)改进版本一(组合代替继承

//扩展操作
class CryptoFileStream{
    FileStream* stream;
public:
    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 CryptoNetworkStream{
    NetworkStream* stream;
public:
    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 CryptoMemoryStream{
    NetworkStream* stream;
public:
    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 CryptoFileStream{
    Stream* stream;  //使用基类,消除了编译时依赖
public:
    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 CryptoNetworkStream{
    Stream* stream;
public:
    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 CryptoMemoryStream{
    Stream* stream;
public:
    virtual char Read(int number){

        //额外的加密操作...
        stream->Read(number);//读内存流
    }
    virtual void Seek(int position){
        //额外的加密操作...
        stream->Seek(position);//定位内存流
        //额外的加密操作...
    }
    virtual void Write(byte data){
        //额外的加密操作...
        stream->Write(data);//写内存流
        //额外的加密操作...
    }
};
我们发现这3个类一样,除了类名之外都相同,所以我们可以进行合并,消除重复性
//扩展操作
class CryptoFileStream{
    Stream* stream;
public:
    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 BufferedFileStream{  //所有相同操作都将去重,只保留一个
//...
};
同时发现一个问题,我们从继承转组合以后的虚函数哪来的?我们还是要遵循留的规范,即基类为我们设置的接口虚函数。我们还是需要进行继承,完善接口规范,不过只需要继承流的基类即可
//扩展操作
class CryptoStream :public Stream{
    Stream* stream;
public:
    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{
    //...
};
下面是全部修改代码
//业务操作
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);
    //上面对s2加密了,我们再进行s2的缓存,实现了既加密又缓存
    BufferedStream* s4=new BufferedStream(s2);
}

运行时装配:

我们编译时不存在文件加密,网络加密,文件缓存,文件加密缓存等操作,我们在运行时对其进行组合装配起来满足我们的需求

另外注意:

根据重构中所说:当我们类中含有重复字段和方法,我们应该将其提到前面基类中去,这里我们若是将Stream* stream提到Stream基类中去,会发现在主体类中会包含这个不需要的字段,所以这个时候我们应该设计一个中间基类。这时就引用出来了装饰模式

(五)改进版本二(使用装饰模式<中间基类>)

//扩展操作
//中间类
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 Seek(int position){
        //额外的加密操作...
        stream::Seek(position);//定位文件流
        //额外的加密操作...
    }
    virtual void Write(byte data){
        //额外的加密操作...
        stream::Write(data);//写文件流
        //额外的加密操作...
    }
};

class BufferedStream : public DecoratorStream{
public:
    BufferedStream(Stream* stm):DecoratorStream(stm){

    }
    //...
};

最终规模是:1+n+1+m

全部代码

//业务操作
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{

public:
    BufferedStream(Stream* stm):DecoratorStream(stm){

    }
    //...
};

void Process(){

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

    CryptoStream* s2=new CryptoStream(s1);

    BufferedStream* s3=new BufferedStream(s1);

    BufferedStream* s4=new BufferedStream(s2);
}
以组合的方式来支持未来多态的变化

(六)模式定义

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

(七)类图(结构)

(八)要点总结

1.通过采用组合而非继承的手法, Decorator模式实现了在运行时 动态扩展对象功能的能力,而且可以根据需要扩展多个功能。避免了使用继承带来的“灵活性差”和“多子类衍生问题”。

2.Decorator类在接口上表现为is-a Component的继承关系,即Decorator类继承了Component类所具有的接口。但在实现上又 表现为has-a Component的组合关系,即Decorator类又使用了 另外一个Component类。

DecoratorStream: public Stream{
protected:
    Stream* stream;//...

    DecoratorStream(Stream * stm):stream(stm){

    }

};
若是程序中一个类的父类和他的字段类是同一个类的,那么他有极大的几率是Decorator模式。因为一般继承就不组合,组合就不继承
而我们这里的继承是为了接口的规范,组合是为了将来支持具体实现类

3.Decorator模式的目的并非解决“多子类衍生的多继承”问题, Decorator模式应用的要点在于解决“主体类在多个方向上的扩展 功能”——是为“装饰”的含义。

主体操作和扩展操作应该分开分之继承 

(九)案例实现(装饰车)

//业务规范
class Car
{
public:
    virtual void show() = 0;
    virtual ~Car(){}
};
//主体类
class RunCar :public Car
{
public:
    virtual void show()
    {
        cout << "running" << endl;
    }
};

class SwimCar :public Car
{
public:
    virtual void show()
    {
        cout << "swimming" << endl;
    }
};

class FlyCar :public Car
{
public:
    virtual void show()
    {
        cout << "flying" << endl;
    }
};
//装饰中间类
class DecoratorCar : public Car
{
protected:
    Car * car;
public:
    DecoratorCar(Car* c) :car(c){}
};
//扩展操作类
class EquipEngine :public DecoratorCar
{
public:
    EquipEngine(Car* c) :DecoratorCar(c)
    {
    }

    virtual void show()
    {
        cout << "equip engine can run fast" << endl;
        car->show();
    }
};

class EquipWing :public DecoratorCar
{
public:
    EquipWing(Car* c) :DecoratorCar(c)
    {
    }

    virtual void show()
    {
        cout << "equip wing can fly" << endl;
        car->show();
    }
};

class EquipPaddle :public DecoratorCar
{
public:
    EquipPaddle(Car* c) :DecoratorCar(c)
    {
    }

    virtual void show()
    {
        cout << "equip paddle can swing" << endl;
        car->show();
    }
};
int main()
{
    SwimCar* car = new SwimCar();
    EquipPaddle *scar = new EquipPaddle(car);
    EquipEngine *ecar = new EquipEngine(scar);
    EquipWing *wcar = new EquipWing(ecar);

    wcar->show();
    system("pause");
    return 0;
}

原文地址:https://www.cnblogs.com/ssyfj/p/9532496.html

时间: 2024-10-06 17:20:56

设计模式---单一职责模式之装饰模式(Decorator)的相关文章

C语言的设计模式-单一职责

C语言的设计模式-单一职责 单一职责原则: 通常的定义是只专注于做一件事和仅有一个引起它变化的原因.对于接口.实现.函数级别往往我们比较容易关注单一职责,大家谈的也比较多,但对于返回值.参数可能不会有太多的人关注.但往往就是这些不符合单一职责原则的设计可能导致一些很难发现的BUG.看看下面这段代码: pBuf = (byte*)realloc( pBuf, size); if( pbBuf != NULL ) { TODO... } 可能很多人一眼看上去并没有什么问题,先让我们看看这个库函数的定

《大话设计模式》 :单一职责模式

单一职责原则,就一个类而言,应该仅有一个引起它变化的原因. 如果一个类承担的职责过多,就等于把这些职责耦合起来,一个职责的变化可能会削弱或者抑制这个类完成其他职责的能力,这种耦合会导致脆弱的设计,当变化发生时,设计会遭受到意想不到的破坏. 软件设计真正要做的许多内容,就是发现职责并把这些职责相互分离. 如果你能够想到多于一个的动机去改变一个类,那么这个类就具有多余一个的职责.

设计模式:代理模式与装饰模式

1.装饰者模式与代理模式 (静态代理) 在日常开发里面,我们经常需要给某个类的方法增加加某些特定的功能. 例如:有婴儿,婴儿会吃饭和走动,如以下类 1 package com.scl.designpattern.proxy; 2 3 //婴儿类 4 public class Child implements Human 5 { 6 public void eat() 7 { 8 System.out.println("eat something...."); 9 } 10 11 @Ov

小菜学设计模式——单一职责原则

背景 本文标题为什么叫小菜学习设计模式,原因是本文内容主要是学习<大话设计模式>时的笔记摘要部分,当然,并不是记录书中小菜的学习过程,这个完全没有意义,而是指本人学习设计模式的成长之旅. 真诚的希望自己能够从一名小菜成长为一名大鸟! 编写的程序应该满足: 1)可维护 2)可扩展 3)可复用 4)够灵活 废话少说,言归正传,设计模式原则之:单一职责 书面理解 单一职责:就一个类而言,应该仅有一个引起它变化的原因 如果一个类承担职责过多,接等于把这些职责耦合在一起,一个职责的变化可能会削弱或者抑制

设计模式-单一职责原则

1.单一职责原则 单一职责原则:改变仅因为一个因素 <设计模式之禅>,作者提到有人写了个这样的接口 void changeUser(UserOB userOB,changeOptions option); 不如分开写 void changeUserName(String userName); void changeUserAddress(String address); void changeUserTel(String Tel); 虽然如作者提到的,下面的替代上面的,到底是不是应该替换呢?看

小王和你一起学习系列之设计模式-单一职责原则

单一职责原则,从字面上理解就是做一件事情. 单一职责原则应用的场景包括: 一个接口只做同一类事情 一个类只做同一类事情 一个方法只做一件事情 一段代码只做一件事情 咱们具体分析各个应用场景吧 一.一段代码 ? 一段代码代表一种逻辑.代码是最细粒度,接口.类.方法都是由代码组成的. 二.一个方法 ? 如果方法中的代码逻辑比较简单,哪怕有分支,有不同的逻辑处理,那么也是允许的.如果方法中的代码逻辑很复杂,各种条件判断,如果以后需求发生变更,导致代码发生更改, 修改的时候必然会小心翼翼,生怕改的代码对

设计模式——单一职责原则

职责过多坏处: 如果一个类承担的职责过多,就等于把这些职责耦合在一起,一个职责的变化可能会削弱或者抑制这个类完成其他职责的能力. 这种耦合会导致脆弱的设计,当变化发生是,设计会遭受到意想不到的破坏. 单一职责: 软件设计真正要做的许多内容,就是发现职责并把那些职责相互分离. 其实要去判断是否应该分离出类来,也不难,拿就是如果你能够想到多于一个的动机去改变一个类,那么这个类就具有多于一个的职责,就应该考虑类的职责的分离. 总结: 在类的职责分离上多思考,做到单一职责,这样你的代码才是真正的易维护,

单一职责模式

单一职责原则(SRP):就一个类而言,应该仅有一个引起它变化的原因. 如果一个类承担的职责过多,就等于把这些职责耦合在一起,一个职责的变化可能会小若或则抑制这个类完成其他职责的能力.这种耦合会当值脆弱的设计,当发生变化时,设计会发生意想不到的破坏. 软件设计真正要做到许多内容,就是发现职责,并把那些职责相互分离.其实要去判断是否应该分离出类来,也不难,那就是如果你能够想到多于一个的动机去改变一个类,那么这个类就具有多余一个的职责,就应该考虑类的职责分离. 编程时,我们在设计类的时候要在类的分离上

大话设计模式---单一职责原则

单一职责原则(SRP):就一个类而言,应该仅有一个引起它变化的原因. 如果一个类承担的职责过多,就等于把这些职责耦合在一起,一个职责的变化可能会削弱或者抑制这个类完成其他职责的能力.这种耦合会导致脆弱的设计,当变化发生时,设计会遭受到意想不到的破坏. 软件设计真正要做的许多内容,就是发现职责并把那些职责互相分离.    如果能够想到多于一个的动机去改变一个类,那么这个类就具有多于一个的职责,就应该考虑类的职责分离.