您的位置:首页 > 其它

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

2017-12-06 18:47 627 查看
之前的学习过程中 学习了单一职责类:

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

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

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

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

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

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

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

其实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 CrytoNetworkStrrea
{
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模式来处理。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息