设计模式 学习笔记 之 装饰模式 Decorator(6)

之前的学习过程中 学习了单一职责类:

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

这时候关键就是划清责任。

今天就具体学习下单一职责中的装饰模式。

动机:在某些情况下我们可能会“过度的使用继承来拓展对象的功能”,由于继承为类型引入的静态特质,是的这种扩展

方式缺乏灵活性,并且随着子类的增多(扩展的功能的增多),各种子类的组合(扩展功能的组合)会导致更多子类的膨胀。

如何使“对象功能的拓展”能够根据需要来动态的实现?同时便面“扩展功能的增多”带来的子类膨胀问题?

从而使得任何“功能扩展的变化”所导致的影响降低!

其实java的IO 处理就是装饰模式实现的。

接下来我们就用C++ 伪代码的 设计我们自己的 IO 代码,按照常规思路去设计的话 首先我们先来抽象接口

 然后通过不同的实体进行子类话,伪代码如下

class Stream { // 定义一组功能操作接口
public :
    virtual char Read(int number) = 0; //读取
    virtual void Seek(int positon) = 0;//定位
    virtual void Write(char date) = 0; //写
    virtual ~Stream(){
    }
};


class FileStream:public Stream{
    //文件流处理类
public :
    virtual char Read(int number) {

    }
    virtual void Seek(int positon) {

    }
    virtual void Write(char date) {

    }
};
class NetworkStrrea: public Stream{
    //网络流
public :
    virtual char Read(int number) {

    }
    virtual void Seek(int positon) {

    }
    virtual void Write(char date) {

    }
};

class MemoryStream: public Stream{
    //内存流
public :
    virtual char Read(int number) {

    }
    virtual void Seek(int positon) {

    }
    virtual void Write(char date) {

    }
};

当新的需求来了现在 需要加密的时候文件流,还需要缓冲流:

class CrytoFileStream :public FileStream
{
public :
    virtual char Read(int number) {
        //额外的加密操作……
        FileStream ::Read(number);
    }
    virtual void Seek(int positon) {
          //额外的加密操作……
        FileStream ::Seek(positon);
    }
    virtual void Write(char date) {
          //额外的加密操作……
        FileStream ::Write(date);
    }
};


class CrytoNetworkStrrea :public NetworkStrrea
{
public :
    virtual char Read(int number) {
        //额外的加密操作……
        NetworkStrrea ::Read(number);
    }
    virtual void Seek(int positon) {
          //额外的加密操作……
        NetworkStrrea ::Seek(positon);
    }
    virtual void Write(char date) {
          //额外的加密操作……
        NetworkStrrea ::Write(date);
    }
};
class CrytoMemoryStream :public MemoryStream
{
public :
    virtual char Read(int number) {
        //额外的加密操作……
        MemoryStream ::Read(number);
    }
    virtual void Seek(int positon) {
          //额外的加密操作……
        MemoryStream ::Seek(positon);
    }
    virtual void Write(char date) {
          //额外的加密操作……
        MemoryStream ::Write(date);
    }
};

class BufferFileStream :public FileStream{
//...
};
class BufferNetworkStrrea :public NetworkStrrea{
//...
};
class BufferMemoryStream :public MemoryStream{
//...
};


用了继承的方式 当新的需求去改变流的时候将会不停的继承成子类。在没有学习装饰模式之前,和容易掉进这样的陷阱。

假设我们现在要有个既有加密又有缓冲的流操作 :

class CrytoBufferFileStream :public FileStream{
//...
};
class CrytoBufferNetworkStrrea :public NetworkStrrea{
//...
};
class CrytoBufferMemoryStream :public MemoryStream{
//...
};


就会又多出三个子类去继承  来张图:

来算下 如果这样设计下来会存在多少个 类呢   1 +n +n *m! /2   现在的 n = 2 ,m= 3; 如果n 和m 都变大了 ,

那真的是个可怕的事情。

既然知道这是个非常可怕,并将来的对维护而言肯定更加困难,

根据设计原则 组合优于继承 我们将代码重构 

class CrytoFileStream
{
    FileStream *fileStream;
public :
    virtual char Read(int number) {
        //额外的加密操作……
        fileStream->Read(number);
    }
    virtual void Seek(int positon) {
          //额外的加密操作……
        fileStream->Seek(positon);
    }
    virtual void Write(char date) {
          //额外的加密操作……
        fileStream->Write(date);
    }
};


class CrytoNetworkStream
{
    NetworkStrrea *networkStrrea;
public :
    virtual char Read(int number) {
        //额外的加密操作……
        networkStrrea->Read(number);
    }
    virtual void Seek(int positon) {
          //额外的加密操作……
       networkStrrea->Seek(positon);
    }
    virtual void Write(char date) {
          //额外的加密操作……
        networkStrrea->Write(date);
    }
};
class CrytoMemoryStream  
{
    MemoryStream *memoryStream;
public :
    virtual char Read(int number) {
        //额外的加密操作……
        memoryStream->Read(number);
    }
    virtual void Seek(int positon) {
          //额外的加密操作……
        memoryStream->Seek(positon);
    }
    virtual void Write(char date) {
          //额外的加密操作……
        memoryStream->Write(date);
    }
};

有多态意识的可以意识到, 使用组合关系的时候,使用的全是子类 ,当大部分变量是某个类型的的子类,就不用声明成子类 ,直接声明父类就好 。

也就是里氏替换原则,

我们可以把编译期的帮绑定替换成运行时的 。

class CrytoFileStream
{
    Stream *stream; //new FileStream();
public :
    virtual char Read(int number) {
        //额外的加密操作……
        stream->Read(number);
    }
    virtual void Seek(int positon) {
          //额外的加密操作……
        stream->Seek(positon);
    }
    virtual void Write(char date) {
          //额外的加密操作……
        stream->Write(date);
    }
};


class CrytoNetworkStrrea
{
     Stream *stream;
public :
    virtual char Read(int number) {
        //额外的加密操作……
        stream->Read(number);
    }
    virtual void Seek(int positon) {
          //额外的加密操作……
       stream->Seek(positon);
    }
    virtual void Write(char date) {
          //额外的加密操作……
        stream->Write(date);
    }
};
class CrytoMemoryStream  
{
    Stream *stream;
public :
    virtual char Read(int number) {
        //额外的加密操作……
        stream->Read(number);
    }
    virtual void Seek(int positon) {
          //额外的加密操作……
        stream->Seek(positon);
    }
    virtual void Write(char date) {
          //额外的加密操作……
        stream->Write(date);
    }
};


到这里以后来一次总结 : 我们可以发现继承和组合的微妙关系 ,编译时复用,运行时去变化。

这也是我们写面向对象代码的真谛 。

仔细来看上面的代码是不是发现竟然是一样的。不一样的在哪里 就是在那个未来 多态去支持变化的。

class CrytoStream  :public Stream
{
    //为什么要继承呢?因为需要接口的规范
    Stream *stream;
public :
    virtual char Read(int number) {
        //额外的加密操作……
        stream->Read(number);
    }
    virtual void Seek(int positon) {
          //额外的加密操作……
        stream->Seek(positon);
    }
    virtual void Write(char date) {
          //额外的加密操作……
        stream->Write(date);
    }
};

class BufferStream :public Stream{
//同理 我们也可以得到一个bufferSteam 缓冲流
};

根据重构理论:如果某一个类有相同的子类就继续往上提

class DecoratorStream :public Stream
{
    Stream * stream;

};
class CrytoStream  :public DecoratorStream
{


public: 
    CrytoStream(Stream  * stream):DecoratorStream(stream){

    }

public :
    virtual char Read(int number) {
        //额外的加密操作……
        stream->Read(number);
    }
    virtual void Seek(int positon) {
          //额外的加密操作……
        stream->Seek(positon);
    }
    virtual void Write(char date) {
          //额外的加密操作……
        stream->Write(date);
    }
};

class BufferStream :public DecoratorStream{
//同理 我们也可以得到一个bufferSteam 缓冲流
public: 
    BufferStream(Stream  * stream):DecoratorStream(stream){

    }
};

通过我们重构以后

他有多少类呢 ?  1+n+1+m

为什么会有怎么大的变化能 就是因为对继承的不良使用 ,这也就是我们的动机。继承引入的静态特质,组合关系引入动态。

通过组合的关系进行支持未来的变化(多态);

GOF 中的装饰模式的定义:

动态(组合)的给一个对象增加一些额外的职责,就增加功能而言,Decorator模式比生成子类(继承)更为灵活,

(消除重复代码&减少子类个数)。

总结:

通过采用组合的而非继承的方式,Decorator模式实现了在运行是动态扩展的功能,而且根据需求扩展多个功能。

避免了使用继承带来的“灵活性”和“子类衍生”问题。

Decorator类设计接口上表现位is-a Componet 继承关系。但是实际上为has-a Componetd的组合关系。

在代码中一旦看到这样的关系 ,我们就可以按照Decorator模式来处理。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值