“对象创建”模式
通过“对象创建”模式绕开直接new一个具体的类型,来避免对象创建(new)过程中所导致的紧耦合(依赖具体类型),从而支持对象创建的稳定。他是接口抽象之后的第一步工作。
- 典型模式
- Factory Method
- Abstract Factory
- Prototype
- Builder
工厂方法 (Factory Method)
定义一个用于创建对象的接口,让子类决定实例化哪个类。Factory Method 使得一个类的实例化延迟(目的:解耦,手段:虚函数)到子类。
——《设计模式》GoF
- 动机
在软件系统中,经常会面临着创建对象的工作,由于需求变化,需要创建的对象的具体类型也会经常的变化
假设还是拿之前的文件分割器作为例子,如果不使用具体的设计模式,那么在具体创建对象的时候,需要在初始化对象的时候指定出具体的实现细节,虽然ISplitter * splitter = new BinarySplitter();
是面向接口的编程,将变量生命为了抽象的基类,但在实际new时,依赖了一个具体的实现类。实际上这里违反了依赖导致原则。如果这时候具体的Splitter还未完成实现,那么编译时会报错,如果需求发生变更那么此时,也需要重新修改和编译源代码。
class ISplitter{
public:
virtual void split()=0;
virtual ~ISplitter(){}
};
class BinarySplitter : public ISplitter{
};
class TxtSplitter: public ISplitter{
};
class PictureSplitter: public ISplitter{
};
class VideoSplitter: public ISplitter{
};
class MainForm : public Form
{
TextBox* txtFilePath;
TextBox* txtFileNumber;
ProgressBar* progressBar;
public:
void Button1_Click(){
ISplitter * splitter=
new BinarySplitter();//依赖具体类
splitter->split();
}
};
对于之前的代码,在应对变化时,ISplitter * splitter = new BinarySplitter();
这里是核心的问题。那么如何才能绕开new一个具体的细节呢?
那么我们可以使用一个函数,来讲所需要的对象返回给我们的方式来获取这个对象,为了代码的进一步隔离,我们把他直接抽象到一个新的类中,并用写成一个虚函数,由工厂的子类来自己实现创建自己的方法。也就形成了一个模式,每个实现方法有一个类,配套的每个类都有一个工厂,而这些工厂都继承自一个抽象的工厂。那么就得到了下面的代码。
//抽象类
class ISplitter{
public:
virtual void split()=0;
virtual ~ISplitter(){}
};
//工厂基类
class SplitterFactory{
public:
virtual ISplitter* CreateSplitter()=0;
virtual ~SplitterFactory(){}
};
//具体类
class BinarySplitter : public ISplitter{
};
class TxtSplitter: public ISplitter{
};
class PictureSplitter: public ISplitter{
};
class VideoSplitter: public ISplitter{
};
//具体工厂
class BinarySplitterFactory: public SplitterFactory{
public:
virtual ISplitter* CreateSplitter(){
return new BinarySplitter();
}
};
class TxtSplitterFactory: public SplitterFactory{
public:
virtual ISplitter* CreateSplitter(){
return new TxtSplitter();
}
};
class PictureSplitterFactory: public SplitterFactory{
public:
virtual ISplitter* CreateSplitter(){
return new PictureSplitter();
}
};
class VideoSplitterFactory: public SplitterFactory{
public:
virtual ISplitter* CreateSplitter(){
return new VideoSplitter();
}
};
class MainForm : public Form
{
SplitterFactory* factory;//工厂
public:
MainForm(SplitterFactory* factory){
this->factory=factory;
}
void Button1_Click(){
ISplitter * splitter=
factory->CreateSplitter(); //多态new
splitter->split();
}
};
现在的工厂通过MainForm的构造函数传入其中,依赖其实依旧存在,但是MainForm并没有具体类型的依赖,而依赖的都是抽象类。其实对于设计模式来说,并没有办法消除依赖,而是将依赖控制在了最可控的地方。
UML要点总结
Factory Method 模式用于隔离类对象的使用者和具体类型之间的耦合关系。面对一个经常变化的具体类型,紧耦合关系(new)会导致软件的脆弱。
Factory Method模式通过面对对象的手法,将所要创建的对象工作延迟到子类,从而实现一种扩展(而非更改)的策略,较好地解决了这种紧耦合关系。
Factory Method模式解决“单个对象”的需求变化。缺点在于要求创建方法/参数相同。
抽象工厂 (Abstract Factory)
提供一个接口,让该接口负责创建一系列“性关系或相互依赖的对象”,无需指定他们的具体类。
——《设计模式》GoF
- 动机
在软件系统中,经常会面临着“一系列相互依赖的对象”的创建工作,同时由于需求的变化,往往存在着更多系列对象的创建工作。
假设需要写一个数据访问层,那么需要创建一系列对象,比如需要connection对象、command对象等等。同时在不同的数据库选择的时候,也存在变化的问题。所以一下代码是写死的代码,不能很好的适应未来变化。
class EmployeeDAO{
public:
vector<EmployeeDO> GetEmployees(){
SqlConnection* connection =
new SqlConnection();
connection->ConnectionString = "...";
SqlCommand* command =
new SqlCommand();
command->CommandText="...";
command->SetConnection(connection);
SqlDataReader* reader = command->ExecuteReader();
while (reader->Read()){
}
}
};
那么,为了应对数据库变化的情况,那么将数据库访问的一些对象进行向上抽象。
//数据库访问有关的基类
class IDBConnection{
};
class IDBConnectionFactory{
public:
virtual IDBConnection* CreateDBConnection()=0;
};
class IDBCommand{
};
class IDBCommandFactory{
public:
virtual IDBCommand* CreateDBCommand()=0;
};
class IDataReader{
};
class IDataReaderFactory{
public:
virtual IDataReader* CreateDataReader()=0;
};
//支持SQL Server
class SqlConnection: public IDBConnection{
};
class SqlConnectionFactory:public IDBConnectionFactory{
};
class SqlCommand: public IDBCommand{
};
class SqlCommandFactory:public IDBCommandFactory{
};
class SqlDataReader: public IDataReader{
};
class SqlDataReaderFactory:public IDataReaderFactory{
};
//支持Oracle
class OracleConnection: public IDBConnection{
};
class OracleCommand: public IDBCommand{
};
class OracleDataReader: public IDataReader{
};
class EmployeeDAO{
IDBConnectionFactory* dbConnectionFactory;
IDBCommandFactory* dbCommandFactory;
IDataReaderFactory* dataReaderFactory;
public:
vector<EmployeeDO> GetEmployees(){
IDBConnection* connection =
dbConnectionFactory->CreateDBConnection();
connection->ConnectionString("...");
IDBCommand* command =
dbCommandFactory->CreateDBCommand();
command->CommandText("...");
command->SetConnection(connection); //关联性
IDBDataReader* reader = command->ExecuteReader(); //关联性
while (reader->Read()){
}
}
};
对于上面的代码来说,把所有的数据库访问有关的对象都创建了基类,如果为了实现其他的数据库,只需要针对对应的基类派生出子类。那么,其实也就是每个对象的创建都使用了工厂模式。
现在已经能够很好的解决变化的问题,但是这时候存在一个新的问题,那就是,这一系列对象并不是彼此分隔的,比如MySQL的Connection对象对应着MySQL的Commend,如果使用错了,那么肯定会在运行时报错,无法完成我们想要达到的目的。那么这个问题该如何解决呢?
我们可以尝试把对应的一系列的对象放在同一个工厂类中,以此来避免出现这样的问题。
//数据库访问有关的基类
class IDBConnection{
};
class IDBCommand{
};
class IDataReader{
};
class IDBFactory{
public:
virtual IDBConnection* CreateDBConnection()=0;
virtual IDBCommand* CreateDBCommand()=0;
virtual IDataReader* CreateDataReader()=0;
};
//支持SQL Server
class SqlConnection: public IDBConnection{
};
class SqlCommand: public IDBCommand{
};
class SqlDataReader: public IDataReader{
};
class SqlDBFactory:public IDBFactory{
public:
virtual IDBConnection* CreateDBConnection()=0;
virtual IDBCommand* CreateDBCommand()=0;
virtual IDataReader* CreateDataReader()=0;
};
//支持Oracle
class OracleConnection: public IDBConnection{
};
class OracleCommand: public IDBCommand{
};
class OracleDataReader: public IDataReader{
};
class EmployeeDAO{
IDBFactory* dbFactory;
public:
vector<EmployeeDO> GetEmployees(){
IDBConnection* connection =
dbFactory->CreateDBConnection();
connection->ConnectionString("...");
IDBCommand* command =
dbFactory->CreateDBCommand();
command->CommandText("...");
command->SetConnection(connection); //关联性
IDBDataReader* reader = command->ExecuteReader(); //关联性
while (reader->Read()){
}
}
};
以上的代码不难看出,把对应的一套工厂封装在了一起,那么,这就是一种Abstract Factory的模式,其实他的名字如果是Family Factory好像更加合适一点。
Abstract Factory UML要点总结
如果没有对应“多系列对象构建”的需求变化,则没有必要使用Abstract Factory 模式,这时候使用简单的工厂完全可以。
“系列对象”指的是在某一特定系列下的对象之间有相互依赖或作用关系。不同系列的对象之间不能相互依赖。
Abstract Factory 模式主要在于应对“新系列”的需求变动,其缺点在于难以应对“新对象”的需求变动。
原型模式(Prototype)
使用原型实例指定创建对象的种类,然后通过拷贝这些原型来创建新的对象。
——《设计模式》GoF
- 动机
在软件系统中,经常面临着“某些结构复杂的对象”的创建工作;由于需求的变化,这些对象经常面临着剧烈的变化,但是他们却拥有着比较稳定一直的接口。
假设将工厂模式中的ISplitter和SplitterFactor两个基类进行合并。并添加一个克隆自己的方式来构建对象的虚方法。使用的时候应该是通过原型对象构造的对象来clone出所需要的对象。
//抽象类
class ISplitter{
public:
virtual void split()=0;
virtual ISplitter* clone()=0; //通过克隆自己来创建对象
virtual ~ISplitter(){}
};
//具体类
class BinarySplitter : public ISplitter{
public:
virtual ISplitter* clone(){
return new BinarySplitter(*this);
}
};
class TxtSplitter: public ISplitter{
public:
virtual ISplitter* clone(){
return new TxtSplitter(*this);
}
};
class PictureSplitter: public ISplitter{
public:
virtual ISplitter* clone(){
return new PictureSplitter(*this);
}
};
class VideoSplitter: public ISplitter{
public:
virtual ISplitter* clone(){
return new VideoSplitter(*this);
}
};
class MainForm : public Form
{
ISplitter* prototype;//原型对象
public:
MainForm(ISplitter* prototype){
this->prototype=prototype;
}
void Button1_Click(){
ISplitter * splitter=
prototype->clone(); //克隆原型
splitter->split();
}
};
Prototype UML
这时候有一个问题,就是原型模式通过克隆的方式获得对象的目的到底是什么呢?这样能够解决什么样的问题呢?
对于某些结构复杂的对象的创建。在某些状态下的工厂会变得比较复杂,并且,创建出来的对象不一定是我们想要的,那么我们只需要使用clone方法就可以得到我们需要的对象。
那么简单一句话就是:本就是你需要从A的实例得到一份与A内容相同,但是又互不干扰的实例的话,就需要使用原型模式。
要点总结
Prototype模式同样用于隔离类对象的使用者和具体类型(易变类)直接的耦合关系,他同样要求这些“易变类”拥有“稳定的接口”。
Prototype模式对于“如何创建易变类的实体对象”采用“原型克隆”的方法来做,它使得我们可以非常灵活地“动态创建”拥有某些稳定接口的新对象——所需工作仅仅是注册一个新类的对象(即原型),然后在任何需要的地方clone。
Prototype模式中的Clone方法可以利用某些框架中的序列化来实现深拷贝。
构建器(Builder)
将一个复杂对象的构建与其表示相分离,使得同样的构建过程(稳定)可以创建不同的表示(变化)。
——《设计模式》GoF
- 动机
在软件系统中,有时候面临着“一个复杂对象”的创建工作,期通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将他们组合在一起的算法却相对稳定。
Builder其实和Template Method有一些相似,而Builder主要是为了解决对象创建的问题。
假设需要构建一个房屋的对象,这个房子的建造过程相对固定的,比如打地基、修墙、装门等等,虽然步骤相对固定,但是具体的墙、门、窗可能都存在差异。
那么使用如下的代码,把固定的部分实现为虚函数的流程(这里的做法很像Template Method)。
具体的房子只需要继承House的类,并重写那些虚方法就行了。
class House{
//....
};
class HouseBuilder {
public:
House* GetResult(){
return pHouse;
}
virtual ~HouseBuilder(){}
protected:
House* pHouse;
virtual void BuildPart1()=0;
virtual void BuildPart2()=0;
virtual void BuildPart3()=0;
virtual void BuildPart4()=0;
virtual void BuildPart5()=0;
};
class StoneHouse: public House{
};
class StoneHouseBuilder: public HouseBuilder{
protected:
virtual void BuildPart1(){
//pHouse->Part1 = ...;
}
virtual void BuildPart2(){
}
virtual void BuildPart3(){
}
virtual void BuildPart4(){
}
virtual void BuildPart5(){
}
};
class HouseDirector{
public:
HouseBuilder* pHouseBuilder;
HouseDirector(HouseBuilder* pHouseBuilder){
this->pHouseBuilder=pHouseBuilder;
}
House* Construct(){
pHouseBuilder->BuildPart1();
for (int i = 0; i < 4; i++){
pHouseBuilder->BuildPart2();
}
bool flag=pHouseBuilder->BuildPart3();
if(flag){
pHouseBuilder->BuildPart4();
}
pHouseBuilder->BuildPart5();
return pHouseBuilder->GetResult();
}
};
Builder UML
- 要点总结
Builder模式主要用于“分步骤构建一个复杂对象”。在这其中“分步骤”是一个稳定算法,而复杂对象的各个部分则经常变化。
变化的点在哪里,封装哪里——Builder模式主要在于应对“复杂对象各个部分”的频繁需求变动。其缺点在于难以应对“分步骤构建算法的需求变动”。
在Builder模式中,要注意不同语言中构造器内调用虚函数的差别(C++ vs. C#)。
网友评论