单一职责类设计模式
在软件设计过程中,如果责任划分不清晰,使得继承得到的结果往往是随着需求变化, 子类急剧膨胀,同时充斥着重复的代码,这个时候的关键是划清责任;
典型模式:
Decorator
Bridge
Decorator
动机:
在某些情况下,会过度的使用继承来拓展对象功能,由于继承为类型引入静态特质,这种拓展方式缺乏灵活性,并且随着子类的增多,各种子类的组合会导致更多的子类膨胀;
所谓静态特质,就是定死了的东西,编译的时候就定好的东西;
//业务操作
class mystream
{
public:
virtual char* Read(const int num) const = 0;
virtual void Seek(const int position) const = 0;
virtual void Write(const char* data) = 0;
virtual ~mystream(){};
};
class FileStream:public mystream
{
public:
char* Read(const int num) const override
{
}
void Seek(const int position) const override
{
}
void Write(const char* data) override
{
}
};
class NETStream :public mystream
{
public:
char* Read(const int num) const override
{
}
void Seek(const int position) const override
{
}
void Write(const char* data) override
{
}
};
class MemoryStream :public mystream
{
public:
char* Read(const int num) const override
{
}
void Seek(const int position) const override
{
}
void Write(const char* data) override
{
}
};
//这里我们希望对文件进行加密操作
class CyferFileStream :public FileStream
{
public:
char* Read(const int num) const override
{
//额外的操作,标识加密
//……
FileStream::Read(num);
}
void Seek(const int position) const override
{
//额外的操作,标识加密
//……
FileStream::Seek(position);
}
void Write(const char* data) override
{
//额外的加密操作
//……
FileStream::Write(data);
}
};
//这里我们希望对文件进行加密操作
class CyferNETStream :public NETStream
{
public:
char* Read(const int num) const override
{
//额外的操作,标识加密
//……
NETStream::Read(num);
}
void Seek(const int position) const override
{
//额外的操作,标识加密
//……
NETStream::Seek(position);
}
void Write(const char* data) override
{
//额外的加密操作
//……
NETStream::Write(data);
}
};
//这里我们希望对文件进行加密操作
class CyferMemoStream :public MemoryStream
{
public:
char* Read(const int num) const override
{
//额外的操作,标识加密
//……
MemoryStream::Read(num);
}
void Seek(const int position) const override
{
//额外的操作,标识加密
//……
MemoryStream::Seek(position);
}
void Write(const char* data) override
{
//额外的加密操作
//……
MemoryStream::Write(data);
}
};
//这里我们又希望对流做一些缓冲操作
class BufferFileStream : public FileStream
{
//……
};
class BufferNETStream : public NETStream
{
//……
};
class BufferMemoryStream : public MemoryStream
{
//……
};
//……后面可能的针对文件流进行既加密又缓冲的操作,就不写了,
整个继承框架如图所示:
如果对象或者操作数增大一点点,整个结构体个数就会急剧增加,但是其实我们仔细观察就会发现,FIleStream,NetStream,以及memorystream里面的read操作,write操作其实都是一样的,代码在大量的重复,复用率非常低,整体效率为O(N*M!);
在真正使用的时候,我们需要用到编译时装配,代码如下:
void process()
{
CyferFileStream* cs = new CyferFileStream();
BufferFileStream* bs = new BufferFileStream();
}
我们可以这样改,利用聚合代替继承,这样保证类与类之间的尽可能独立
class mystream
{
public:
virtual char* Read(const int num) const = 0;
virtual void Seek(const int position) const = 0;
virtual void Write(const char* data) = 0;
virtual ~mystream(){};
};
class FileStream :public mystream
{
public:
char* Read(const int num) const override
{
}
void Seek(const int position) const override
{
}
void Write(const char* data) override
{
}
};
class NETStream :public mystream
{
public:
char* Read(const int num) const override
{
}
void Seek(const int position) const override
{
}
void Write(const char* data) override
{
}
};
class MemoryStream :public mystream
{
public:
char* Read(const int num) const override
{
}
void Seek(const int position) const override
{
}
void Write(const char* data) override
{
}
};
//这里我们希望对文件进行加密操作
class CyferFileStream
{
mystream *stream;//= new FileStream;;
public:
char* Read(const int num) const
{
//额外的操作,标识加密
//……
stream-> Read(num);
}
void Seek(const int position) const
{
//额外的操作,标识加密
//……
stream->Seek(position);
}
void Write(const char* data)
{
//额外的加密操作
//……
stream->Write(data);
}
};
//这里我们希望对文件进行加密操作
class CyferNetStream
{
mystream *stream;//=new NETStream;
public:
char* Read(const int num) const
{
//额外的操作,标识加密
//……
stream->Read(num);
}
void Seek(const int position) const
{
//额外的操作,标识加密
//……
stream->Seek(position);
}
void Write(const char* data)
{
//额外的加密操作
//……
stream->Write(data);
}
};
//这里我们希望对文件进行加密操作
class CyferMemoStream
{
mystream *stream;//=new MemoryStream;
public:
char* Read(const int num) const
{
//额外的操作,标识加密
//……
stream->Read(num);
}
void Seek(const int position) const
{
//额外的操作,标识加密
//……
stream->Seek(position);
}
void Write(const char* data)
{
//额外的加密操作
//……
stream->Write(data);
}
};
然后我们发现,其实后面三个加密操作内容一模一样,前提是如果加密的方式一样的话,那么我们其实可以用一个类来代替下面三个加密类,只需要在构造函数中传入不同的子类对象就行;
class CyferStream
{
mystream *stream;//=new MemoryStream;
public:
CyferStream(mystream* tempstream )
{
stream = tempstream;
}
char* Read(const int num) const
{
//额外的操作,标识加密
//……
stream->Read(num);
}
void Seek(const int position) const
{
//额外的操作,标识加密
//……
stream->Seek(position);
}
void Write(const char* data)
{
//额外的加密操作
//……
stream->Write(data);
}
};
同样的,buffer也是只用一个就够了,但是还有个问题,就是接口不规范,因此我们做如下修改:
class CyferStream:public mystream
{
mystream *stream;//=new MemoryStream;
public:
CyferStream(mystream* tempstream )
{
stream=tempstream;
}
char* Read(const int num) const override
{
//额外的操作,标识加密
//……
stream->Read(num);
}
void Seek(const int position) const override
{
//额外的操作,标识加密
//……
stream->Seek(position);
}
void Write(const char* data)override
{
//额外的加密操作
//……
stream->Write(data);
}
};
注意,上面继承mystream只是为了实现接口一致,没其他目的;
这样一来,我们在调用时就可以这样写:
void process()
{
FileStream *f1 = new FileStream();
CyferStream *cs = new CyferStream(f1);//此处是调用构造而不是拷贝……c艹基础
BufferStream *bs = new BufferStream(cs);//既加密又缓冲,不太懂为什么这里会构造成功……
}
最后我们发现,在三个Cyfer的自定义类中有相同成员对象,应该提到父类中作为成员对象,但是如果提到stream中,定义一个stream,三个自定义stream其实根本不需要这个对象,因此我们要构造一个中间类,中间类的设计如下:
class DecoratorStream : public mystream
{
protected:
mystream* stream;
DecoratorStream(mystream* strm) :stream(strm){};
};
同时cyfer类做出如下修改
class CyferStream :public DecoratorStream
{
public:
CyferStream(mystream* tempstream) :DecoratorStream(tempstream)
{
}
char* Read(const int num) const override
{
//额外的操作,标识加密
//……
return stream->Read(num);
}
void Seek(const int position) const override
{
//额外的操作,标识加密
//……
stream->Seek(position);
}
void Write(const char* data)override
{
//额外的加密操作
//……
stream->Write(data);
}
};
其实本质上就是一个扩展操作,相当于一层一层的加边框,整个类结构就变成了这样
这样一来,m种类的n种操作其实一共就只定义了O(M+N)个类,大大增加了代码的复用性,究其原因,其实很多重复代码都是因为对继承的不良使用造成的
特点:
既继承又组合
在接口上表现是is-a的关系,
但是在实现上表现出has-a的关系。
注意:
并不是多继承就用decorator,装饰者模式的精髓在于主体(比如filestream这些)和对主体的操作(比如加密操作)一定要分开,在于隔离变化!!!!!
bridge
动机:由于某些类型的固有实现逻辑,使得他们有多个变化维度,如何使用面向对象设设计来应对两个甚至多个方向变化,而不引入额外的复杂度?
(这种话看了云里雾里没关系,直接看代码更容易理解)
For example~
class PCMessagerBase :public Messager
{
public:
void playSound()const override{};
void Drawshape() override {};
void writeText() override {};
void connet() override {};
};
class MobileMessagerBase :public Messager
{
public:
void playSound()const override{};
void Drawshape() override {};
void writeText() override {};
void connet() override {};
};
//假如此时,我们需要根据不同的客户需求推出不同的版本;
//比如在PC上有精简版和完整版
//那么我们应该继续继承虚类然后重写接口函数
class PCLite :public PCMessagerBase
{
public:
void myLogin(const string& username, const string& passcode)override
{
PCMessagerBase::connet();
};
void mySendMessage(const string& message)override
{
PCMessagerBase::writeText();
};
void mySendPicture(const string& path)override
{
PCMessagerBase::Drawshape();
};
};
class PCFull :public PCMessagerBase
{
public:
void myLogin(const string& username, const string& passcode)override
{
PCMessagerBase::playSound();
PCMessagerBase::connet();
};
void mySendMessage(const string& message)override
{
PCMessagerBase::playSound();
PCMessagerBase::writeText();
};
void mySendPicture(const string& path)override
{
PCMessagerBase::playSound();
PCMessagerBase::Drawshape();
};
};
//同样我们针对手机版也出俩一样的版本
class IOSLite :public MobileMessagerBase
{
public:
void myLogin(const string& username, const string& passcode)override
{
MobileMessagerBase::connet();
};
void mySendMessage(const string& message)override
{
MobileMessagerBase::writeText();
};
void mySendPicture(const string& path)override
{
MobileMessagerBase::Drawshape();
};
};
class IOSFull :public MobileMessagerBase
{
public:
void myLogin(const string& username, const string& passcode)override
{
MobileMessagerBase::playSound();
MobileMessagerBase::connet();
};
void mySendMessage(const string& message)override
{
MobileMessagerBase::playSound();
MobileMessagerBase::writeText();
};
void mySendPicture(const string& path)override
{
MobileMessagerBase::playSound();
MobileMessagerBase::Drawshape();
};
};
类图其实跟上面的差不多, 就一个虚基类出来俩基类,每个基类下再出俩衍生类;
一共的类的数目为O(NM),N为基类对象个数,M为每个基类衍生类个数
然后我们发现其实跟刚才一样,衍生类种很多东西都是重复的,代码的复用率非常低,因此我们可以仿造之前装饰者模式,把继承改为聚合/复合/组合(怎么叫都行);
具体代码给出一个例子,如果你能理解装饰着模式,下面的代码会非常好理解
class FullVersion:public Messager
{
Messager *Mbase;
public:
FullVersion(Messager *base) :Mbase(base){};
void myLogin(const string& username, const string& passcode)override
{
Mbase->playSound();
Mbase->connet();
};
void mySendMessage(const string& message)override
{
Mbase->playSound();
Mbase->writeText();
};
void mySendPicture(const string& path)override
{
Mbase->playSound();
Mbase->Drawshape();
};
};
此时我们就可以通过指定对象的方式调用不同的重载的内部函数,从而在PC和Mobile上实现功能区分;这样一来只需要写出Lite和full两个class 就能实现地址晚绑定;
当然你再搓个火球做出个中间类也行,就不写了。
然而……这样的想法很美好,但是实际上成员对象Mbase根本无法指定任何对象;因为不管是基类还是虚基类都是抽象类,根本无法实例化……,那么该怎么办呢?
答案其实很简单,不用什么高大上的语法,把接口拆分开就行,比如这样:
class Messager
{
public:
//三个接口对应的内部操作
virtual void playSound()const = 0;
virtual void Drawshape() = 0;
virtual void writeText() = 0;
virtual void connet() = 0;
virtual ~Messager(){};
};
class MessagerIMP
{
public:
//三个接口
virtual void myLogin(const string& username, const string& passcode) = 0;
virtual void mySendMessage(const string& message) = 0;
virtual void mySendPicture(const string& path) = 0;
virtual ~MessagerIMP(){};
};
那么其中衍生类就可以变成这样:
class FullVersion :public MessagerIMP
{
Messager *Mbase;
public:
FullVersion(Messager *base) :Mbase(base){};
void myLogin(const string& username, const string& passcode)override
{
Mbase->playSound();
Mbase->connet();
};
void mySendMessage(const string& message)override
{
Mbase->playSound();
Mbase->writeText();
};
void mySendPicture(const string& path)override
{
Mbase->playSound();
Mbase->Drawshape();
};
};
class LiteVersion :public MessagerIMP
{
Messager *Mbase;
public:
LiteVersion(Messager *base) :Mbase(base){};
void myLogin(const string& username, const string& passcode)override
{
Mbase->connet();
};
void mySendMessage(const string& message)override
{
Mbase->writeText();
};
void mySendPicture(const string& path)override
{
Mbase->Drawshape();
};
};
这个时候我们又发现,其实Lite和Full都有Messager *Mbase;,因此改为下面这样:
class Messager
{
public:
//三个接口对应的内部操作
virtual void playSound()const = 0;
virtual void Drawshape() = 0;
virtual void writeText() = 0;
virtual void connet() = 0;
virtual ~Messager(){};
};
class MessagerIMP
{
protected:
Messager *Mbase;
public:
//三个接口+构造函数
MessagerIMP(Messager* input) :Mbase(input){};
virtual void myLogin(const string& username, const string& passcode) = 0;
virtual void mySendMessage(const string& message) = 0;
virtual void mySendPicture(const string& path) = 0;
virtual ~MessagerIMP(){};
};
class FullVersion :public MessagerIMP
{
public:
FullVersion(Messager *base) :MessagerIMP(base){};
void myLogin(const string& username, const string& passcode)override
{
Mbase->playSound();
Mbase->connet();
};
void mySendMessage(const string& message)override
{
Mbase->playSound();
Mbase->writeText();
};
void mySendPicture(const string& path)override
{
Mbase->playSound();
Mbase->Drawshape();
};
};
class LiteVersion :public MessagerIMP
{
public:
LiteVersion(Messager *base) :MessagerIMP(base){};
void myLogin(const string& username, const string& passcode)override
{
Mbase->connet();
};
void mySendMessage(const string& message)override
{
Mbase->writeText();
};
void mySendPicture(const string& path)override
{
Mbase->Drawshape();
};
};
那么调用的时候只需要这样写
void process()
{
Messager *mes1 = new PCMessagerBase();
LiteVersion *mes2 = new LiteVersion(mes1);
}
此时类的个数仍然是O(M+N)个,因此也大大减少了类的个数;
总结一下:
回到刚才的话题,面对多个变化的维度,其实精髓就在于将变化分离;
有多少个变化的方向,就分成多少个基类,然后用抽象的指针指向它就行;
通常,我们不推荐在继承方案中使用多继承,因为多继承往往会违背单一职责原站;
我们往往用一个继承和多个组合方式来灵活应对不同的变化