设计模式——对象创建类及经典案例

对象创建类设计模式

通过对象创建绕开new,来避免创建过程中的紧耦合,从而支持对象创建的稳定,它是接口抽象后的第一步工作。
典型模式:
factory method
abstract factory
prototype
builder

factory method

动机:
在软件系统中,经常面临着创建对象的工作,由于需求的变化,需要创建的对象的具体类型也经常变化。

在之前的策略模式中第一次遇到过工厂模式,传送门这里
从头讲起,我同样以文件分割为例子,关键代码段如下:

#include<string>
using std::string;
class ui
{
public:
	QPushButton pushbuttom;
};
//假设一个文件分片的小软件,有一个按钮,按一下就实现分片
class mainform :public ui//主界面
{
	string path; //需要分片的文件路径
	int num;//文件分片的数量
public:
	//假设点击了一个按钮,则发生,qt的函数
	connect(ui.pushbuttom, &QPushButton::clicked, this, [=](){
		filespliter *spliter=new filespliter(path, num);
		spliter->split();
	});
};
class filespliter
{
private:
	string path;
	int num;
public:
	filespliter(string str, int innum) :path(str), num(innum){};
	void split()
	{
		//读取文件
		//.......
		//分批次写入
		for (int i = 0; i < num; i++)
		{
			//....
		}
	}
};

这是最原始的观察者模式之前的代码;我们注意看这一行:

filespliter *spliter=new filespliter(path, num);

我们发现其实这违背了设计原则针对接口编程而不是针对实现编程,即
不将对象声明为具体的类而是声明为某个接口
客户程序无需知道对象的具体类型只需要知道接口
从而减少系统中的各部分依赖关系,从而实现高聚合,松耦合设计方案

因此说白了就是不能声名具体类而因该声明为抽象;当然前提是这个模块再未来的更新过程中可能有变化。

比如说我们这里有需求变化,比如说我们除了文件分割之外,还有比如说文本文件分割以及图片分割操做,甚至视频文件分割,那么相应的我们应该加入以下类:

 class textspliter
{
private:
	string path;
	int num;
public:
	textspliter(string str, int innum) :path(str), num(innum){};
	void split()
	{
		//读取文件
		//.......
		//分批次写入
		for (int i = 0; i < num; i++)
		{
			//....
		}
	}
};
class picturespliter
{
private:
	string path;
	int num;
public:
	picturespliter(string str, int innum) :path(str), num(innum){};
	void split()
	{
		//读取文件
		//.......
		//分批次写入
		for (int i = 0; i < num; i++)
		{
			//....
		}
	}
};
class videospliter
{
private:
	string path;
	int num;
public:
	videospliter(string str, int innum) :path(str), num(innum){};
	void split()
	{
		//读取文件
		//.......
		//分批次写入
		for (int i = 0; i < num; i++)
		{
			//....
		}
	}
};

其实这个时候我们发现有很多种分割方式,因此我们应该抽象出一个基类;

class Ispliter
{
protected:
   string path;
   int num;
public:
   Ispliter(string path, int number) :path(path), num(number){};
   virtual void split() = 0;
   virtual ~Ispliter(){};
};

因此分割类代码就应该这样写,以video为例:

class videospliter: public Ispliter
{
public:
	videospliter(string str, int innum) :Ispliter(str,innum){};
	void split() override
	{
		//读取文件
		//.......
		//分批次写入
		for (int i = 0; i < num; i++)
		{
			//....
		}
	}
};

其他类也做对应操作,进而原new操作需要这样更改:

class mainform :public ui//主界面
{
	string path; //需要分片的文件路径
	int num;//文件分片的数量
public:
	//假设点击了一个按钮,则发生,qt的函数
	connect(ui.pushbuttom, &QPushButton::clicked, this, [=](){
		Ispliter *spliter = new filespliter(path, num);//依赖具体类
		spliter->split();
	});
};

然后我们又发现,其实new出来的还是细节依赖,就是说仍然没有遵循依赖倒置原则,及时其他地方都是抽象依赖,只要有一个地方是细节依赖 ,就是违背了该原则。因此我们需要绕开new,这就是涉及到我们的工厂模式

所谓工厂模式,就是利用人为的方式返回一个我们需要的类

class SpliterFactory
{
public:
	virtual Ispliter* CreateSpliter(string path, int number) = 0;
	virtual ~SpliterFactory(){};
};
class filespliterFactory :public SpliterFactory
{
public:
	Ispliter*CreateSpliter(string path, int number) override
	{
		return new filespliter(path, number);
	}
};
class textspliterFactory :public SpliterFactory
{
public:
	Ispliter*CreateSpliter(string path, int number) override
	{
		return new textspliter(path, number);
	}
};
class picturespliterFactory :public SpliterFactory
{
public:
	Ispliter*CreateSpliter(string path, int number) override
	{
		return new picturespliter(path, number);
	}
};
class videospliterFactory :public SpliterFactory
{
public:
	Ispliter*CreateSpliter(string path, int number) override
	{
		return new videospliter(path, number);
	}
};

这样调用的时候,就这样写:

class mainform :public ui//主界面
{
	string path; //需要分片的文件路径
	int num;//文件分片的数量
	SpliterFactory *factroy;//一个工厂就够了
public:
	mainform(SpliterFactory* fac, string str, int inputnum) :factroy(fac), path(str), num(inputnum){};
	//假设点击了一个按钮,则发生,qt的函数
	connect(ui.pushbuttom, &QPushButton::clicked, this, [=](){
		Ispliter *spliter = factroy->CreateSpliter(path, num);
		spliter->split();
	});
};

通过这种方式,我们实际上并没有消灭变化和依赖,我们只是把变化赶到了接口的位置,让人们可以再调用的时候,再具体指定需要产生什么类,这样就实现了隔离变化。

值得注意的是,我们并不需要很多个工厂,因为不会对同一种文件进行多种不同的分割方式同时间进行,如果真有这种需求。只需要创建多个mainform就行了;所以不加vector~

总结,工厂模式主要是用于隔离类对象使用者和具体类型之间的耦合关系。
缺点:要求所有对象创建参数相同

abstract factory

动机: 在软件开发过程中,常常面临着一系列相互依赖的对象的创建工作,同时由于需求变化,往往存在更多系列对象的创建工作。
上代码:

#include<vector>
using namespace std;
class EmployeeDAO
{
public:
	vector<EmployeeDO> getEmployees()
	{
		Sqlconnection* connection = new Sqlconnection();
		connection->connectingtoString = "......";
		
		Sqlcommand* command = new Sqlcommand();
		command->commandtext = "......";

		SqlDataReder* reader = new SqlDataReder();
		while (reader->read())
		{
		
		}
	}
};

学过数据库的人应该能看出来这里其实是对数据库的一个访问层,但是假如说客户今天要针对SQL,明天就要oracle,后天要其他的,那我们整套实现都要重新写,因此我们希望把具体的指定类型往接口上推,也就是构造函数上推。
首先我们需要写一大堆基类:

//数据库访问相关的虚基类
class IDBconnection
{
public:
	virtual void connectingtoString()= 0;
	virtual ~IDBconnection(){};
};
class IDBcommond
{
public:
	virtual void commandtext() = 0;
	virtual ~IDBcommond(){};
};
class IDBDatareader
{
public:
	virtual bool read() = 0;
	virtual ~IDBDatareader(){};
};
//下面是支持SQL库的
class Sqlconnection : public IDBconnection
{
public:
	void connectingtoString(){};
};
class Sqlcommand :public IDBcommond
{
public:
	void commandtext(){};
};
class SqlDataReder :public IDBDatareader
{
public:
	bool read(){};
};
//下面是支持Oracle的
class Oracleconnection : public IDBconnection
{
public:
	void connectingtoString(){};
};
class Oraclecommand :public IDBcommond
{
public:
	void commandtext(){};
};
class OracleDataReder :public IDBDatareader
{
public:
	bool read(){};
};

即使这样,new其实是有问题的,因此我们用用工厂模式,加入以下类:

//工厂
class IDBconnectionFactory
{
public:
	virtual IDBconnection* CreatDBconnnection() = 0;
};
class IDBcommondFactory
{
public:
	virtual IDBcommond* CreatDBcommond() = 0;
};
class IDBReaderFactory
{
public:
	virtual IDBDatareader* CreatDBDataReader() = 0;
};
//SQL实现创造工厂
class SQLconnectionfactory :public IDBconnectionFactory
{
	//IDBconnection重写
};
class SQLcommondfactory :public IDBcommondFactory
{
	//CreatDBcommond重写
};
class SQLconnectionfactory :public IDBReaderFactory
{
	//CreatDBDataReader重写
};
//Oracal实现工厂
class Oracalconnectionfactory :public IDBconnectionFactory
{
	//IDBconnection重写
};
class Oracalcommondfactory :public IDBcommondFactory
{
	//CreatDBcommond重写
};
class Oracalconnectionfactory :public IDBReaderFactory
{
	//CreatDBDataReader重写
};

然后我们就可以这样调用:

class EmployeeDAO
{
	IDBconnectionFactory* IDBcon;
	IDBcommondFactory* IDBcom;
	IDBReaderFactory* IDBrea;
public:
	EmployeeDAO(IDBconnectionFactory* IDBcon1,
		IDBcommondFactory* IDBcom1,
		IDBReaderFactory* IDBrea1) :IDBcon(IDBcon1), IDBcom(IDBcom1), IDBrea(IDBrea1){};
	vector<EmployeeDO> getEmployees()
	{
		IDBconnection* connection = IDBcon->CreatDBconnnection();
		connection->connectingtoString = "......";

		IDBcommond* command = IDBcom->CreatDBcommond();
		command->commandtext = "......";

		IDBDatareader* reader = IDBrea->CreatDBDataReader();
		while (reader->read())
		{

		}
	}
};

这样看,其实问题确实解决了,但是又有这个问题:
当你在IDBcon赋值SQL的connection后,按道理来说是不能把IDBcom赋值orical的commond;

也就是说,要传入值都应该是一整个系类的值,但是使用者可能并没有意识到这一点,因此我们需要利用抽象工厂模式解决这个问题。

class IDBFactory
{
public:
	virtual IDBconnection* CreatDBconnnection() = 0;
	virtual IDBcommond* CreatDBcommond() = 0;
	virtual IDBDatareader* CreatDBDataReader() = 0;
};
//SQL实现创造工厂
class SQLfactory :public IDBconnectionFactory
{
	//IDBconnection重写
	//CreatDBcommond重写
	//CreatDBDataReader重写
};
//Oracal实现工厂
class Oracalfactory :public IDBconnectionFactory
{
	//IDBconnection重写
	//CreatDBcommond重写
	//CreatDBDataReader重写
};

这样我们就用一个工厂代替了三个工厂,代码看起来优雅很多,调用的时候这样写就行:

class EmployeeDAO
{
	IDBFactory* DBfactory;
public:
	EmployeeDAO(IDBFactory* DBfactory) :DBfactory(DBfactory){};
	vector<EmployeeDO> getEmployees()
	{
		IDBconnection* connection = DBfactory->CreatDBconnnection();
		connection->connectingtoString = "......";

		IDBcommond* command = DBfactory->CreatDBcommond();
		command->commandtext = "......";

		IDBDatareader* reader = DBfactory->CreatDBDataReader();
		while (reader->read())
		{

		}
	}
};

这就是抽象工厂,名字取的不是很好但是里面意思理解了就行。

总结:
由于一系列相互依赖对象的情况,比如说这里面的SQL是一个系列,oracal是一个系列,他们又共同继承于IDB系列,因此我们只需要对一系列的对象创建利用他们抽象类IDB系类进行工厂化设计能实现隔离变化。

	他与工厂模式的差异在于,它利用了一个接口创建类一系列(多个相互依赖的)类,工厂模式只创建一个。

prototype

动机:
在软件设计中,常常面临着某些结构复杂的对象的创建工作,由于需求的变化,这些对象经常面临着距离的变化,但是他们又拥有稳定更多接口。
那么我们应该如何应对这种变化,隔离出这种变化。

我们回到之前文件分割的例子,上已经工厂化后的代码:

#include<string>
using namespace std;
//基类
class Ispliter
{
protected:
	string path;
	int num;
public:
	Ispliter(string path, int number) :path(path), num(number){};
	virtual void split() = 0;
	virtual ~Ispliter(){};
};
class SpliterFactory
{
public:
	virtual Ispliter* CreateSpliter(string path, int number) = 0;
	virtual ~SpliterFactory(){};
};
//实现
class picturespliter : public Ispliter
{
public:
	picturespliter(string str, int innum) :Ispliter(str, innum){};
	void split() override
	{
		//读取文件
		//.......
		//分批次写入
		for (int i = 0; i < num; i++)
		{
			//....
		}
	}
};
class videospliter : public Ispliter
{
public:
	videospliter(string str, int innum) :Ispliter(str, innum){};
	void split() override
	{
		//读取文件
		//.......
		//分批次写入
		for (int i = 0; i < num; i++)
		{
			//....
		}
	}
};
class videospliterFactory :public SpliterFactory
{
public:
	Ispliter*CreateSpliter(string path, int number) override
	{
		return new videospliter(path, number);
	}
};
class picturespliterFactory :public SpliterFactory
{
public:
	Ispliter*CreateSpliter(string path, int number) override
	{
		return new picturespliter(path, number);
	}
};

如果我们对基类做出以下修改:

class Ispliter
{
protected:
	string path;
	int num;
public:
	Ispliter(string path, int number) :path(path), num(number){};
	virtual void split() = 0;
	virtual Ispliter* clone(string path, int number) = 0;//通过克隆自己创建对象
	virtual ~Ispliter(){};
};

把工厂改为克隆,那么对应的实现以picturespliter 为例:

class picturespliter : public Ispliter
{
public:
	Ispliter* clone(string str, int innum) 
	{
		this->path = str;
		this->num = innum;
		return new picturespliter(*this);//深拷贝,这里自己写深拷贝,对于指针重新分配空间就行,
		//如果没有指针默认的就行
	};
	void split() override
	{
		//读取文件
		//.......
		//分批次写入
		for (int i = 0; i < num; i++)
		{
			//....
		}
	}
};

那么调用就应该这样:

class mainform :public ui//主界面
{
	string path; //需要分片的文件路径
	int num;//文件分片的数量
	Ispliter *prototype;//原型对象,只能克隆不能直接使用
public:
	mainform(Ispliter* proto, string str, int inputnum) :prototype(proto), path(str), num(inputnum){};
	//假设点击了一个按钮,则发生,qt的函数
	connect(ui.pushbuttom, &QPushButton::clicked, this, [=](){
		Ispliter *spliter = prototype->clone(path, num);
		spliter->split();
	});
};

总结:
同样用于隔离类对象的使用者和具体类型之间的耦合关系,它同样要求这些易变类又稳定的接口,这里稳定的接口是指split函数。这种模式用得比较少。

builder

动机:
在软件系统中,有时候面临着复杂对象的创建工作,其通常由各个部分子对象用一定算法组成,但是各个部分子对象往往变化,而算法却相对稳定,我们应该复合隔离出这种变化?
上代码:

class house
{
public:
	int part1;
	int part2;
	void init()
	{
		//按照一定方式搭建房子的5个部分
		buildpart1();
		buildpart2();
		buildpart3();
		buildpart4(); 
		buildpart5();
	}

protected:
	virtual void buildpart1() = 0;
	virtual void buildpart2() = 0;
	virtual void buildpart3() = 0;
	virtual void buildpart4() = 0;
	virtual void buildpart5() = 0;
};

注意,init不能写成构造函数,因为其实如果构造函数调用虚函数会实现静态绑定。
原因简单说明:因为调用子类构造函数之前会调用父类的构造函数,但是这个时候子类的重写函数还没有被编译,我们却希望调用它,就会报错!!! 这只是c++这样规定……

我们比如说想在房子基础上定义不同类型的房子比如说这样:

class stonehous :public house
{
private:
	int part5;
	int part6;
protected:
	virtual void buildpart1() override
	{
		part1 = 1;
	};
	virtual void buildpart2() override{};
	virtual void buildpart3() override{};
	virtual void buildpart4() override{};
	virtual void buildpart5() override{};
};
class crystalhous :public house
{
private:
	int part3;
	int part4;
protected:
	virtual void buildpart1() override
	{
		part1 = 1;
	};
	virtual void buildpart2() override{};
	virtual void buildpart3() override{};
	virtual void buildpart4() override{};
	virtual void buildpart5() override{};
};

那么我们使用就可以这样写:

int pro()
{
	house* home = new stonehous();
	home->init();
}

根据某个人的理论,house的类里面实现的功能太多了,因此我们应该拆分,把算法部分整个拎出去作为一个类,这样就有house和housebuilder两个类

class house
{
public:
	int part1;
	int part2;
	//.....
};
class crystalhous :public house
{
public:
	int part3;
	int part4;
};
class housebuilder
{
public:
	void init()
	{
		//按照一定方式搭建房子的5个部分
		buildpart1();
		buildpart2();
		buildpart3();
		buildpart4();
		buildpart5();
	}
	virtual ~housebuilder(){};
protected:
	house *home;
	virtual void buildpart1() = 0;
	virtual void buildpart2() = 0;
	virtual void buildpart3() = 0;
	virtual void buildpart4() = 0;
	virtual void buildpart5() = 0;
};
class crystalhousbuilder :public housebuilder
{
protected:
	virtual void buildpart1() override
	{
		home->part1 = 1;
		//...等等等操作
	};
	virtual void buildpart2() override{};
	virtual void buildpart3() override{};
	virtual void buildpart4() override{};
	virtual void buildpart5() override{};
};

本来这样就行了,有些人比较无聊,觉得这个算法部分init既然非常稳定,那么可以全部提出去,再做一个拆分,做到进一步的单一职责原则,因此就出现了下面这个复杂一点点的版本。

class house
{
public:
	int part1;
	int part2;
	//.....
};
class crystalhous :public house
{
public:
	int part3;
	int part4;
};
class housebuilder
{
public:
	housebuilder(house* house) :home(house){};
	house* gethous()
	{
		return home;
	}
	virtual ~housebuilder(){};
	virtual void buildpart1() = 0;
	virtual void buildpart2() = 0;
	virtual void buildpart3() = 0;
	virtual void buildpart4() = 0;
	virtual void buildpart5() = 0;
protected:
	house *home;
};
class crystalhousbuilder :public housebuilder
{
protected:
	virtual void buildpart1() override
	{
		home->part1 = 1;
		//...等等等操作
	};
	virtual void buildpart2() override{};
	virtual void buildpart3() override{};
	virtual void buildpart4() override{};
	virtual void buildpart5() override{};
};
class housedirector
{
	housebuilder* builder;
public:
	housedirector(housebuilder* builder) :builder(builder){};
	house* construct()
	{
		//按照一定方式搭建房子的5个部分
		builder->buildpart1();
		builder->buildpart2();
		builder->buildpart3();
		builder->buildpart4();
		builder->buildpart5();
		return builder->gethous();
	}
};

这个时候我们就可以这样调用

void process()
{
	house* home = new crystalhous();
	housebuilder *builder = new crystalhousbuilder(home);
	housedirector director(builder);
	director.construct();
}

说白了,其实就是将一个复杂对象变化的构建和表示分离,使得同样的构建过程可以表示不同的变化。
这种模式适用于小步骤和内容都在变化的类,但是总体框架不变的类。
这种模式用得也比较少……

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

无情の学习机器

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

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

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

打赏作者

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

抵扣说明:

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

余额充值