设计模式——单一职责类及经典案例

单一职责类设计模式

在软件设计过程中,如果责任划分不清晰,使得继承得到的结果往往是随着需求变化, 子类急剧膨胀,同时充斥着重复的代码,这个时候的关键是划清责任;
典型模式:
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)个,因此也大大减少了类的个数;

总结一下:
回到刚才的话题,面对多个变化的维度,其实精髓就在于将变化分离;
有多少个变化的方向,就分成多少个基类,然后用抽象的指针指向它就行;

通常,我们不推荐在继承方案中使用多继承,因为多继承往往会违背单一职责原站;
我们往往用一个继承和多个组合方式来灵活应对不同的变化
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

无情の学习机器

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值