设计模式——对象创建类及经典案例
作者:互联网
对象创建类设计模式
通过对象创建绕开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();
}
说白了,其实就是将一个复杂对象变化的构建和表示分离,使得同样的构建过程可以表示不同的变化。
这种模式适用于小步骤和内容都在变化的类,但是总体框架不变的类。
这种模式用得也比较少……
标签:int,void,virtual,public,案例,path,类及,设计模式,class 来源: https://blog.csdn.net/weixin_42034081/article/details/115391884