本文讲述了对象创建模式中的抽象工厂模式,介绍了抽象工厂模式动机、定义、结构、代码实例,最后进行了总结。
再次感谢GeekBand的李建忠老师、GOF等前辈
1. “对象创建”模式
- 通过“对象创建” 模式绕开new,来避免对象创建(new)过程中所导致的紧耦合(依赖具体类),从而支持对象创建的稳定。它是接口抽象之后的第一步工作。
- 典型模式
- Factory Method
- Abstract Factory 抽象工厂
- Prototype
- Builder
2. 动机
- 在软件系统中,经常面临着“一系列相互依赖的对象[通过代码详细解释]”的创建工作;同时,由于需求的变化,往往存在更多系列对象的创建工作[通过代码详细解释]。
- 如何应对这种变化?如何绕过常规的对象创建方法(new),提供一种“封装机制”来避免客户程序和这种“多系列具体对象创建工作”的紧耦合?
3. 代码实例
代码EmployeeDAO.cpp
实现的是mysql
数据库访问,其中SqlConnection
用于与mysql
建立连接,SqlCommand
用于记录sql
命令,SqlDataReader
用于读取查询结果。但程序明显违背了面向接口编程与依赖倒置原则,如下程序做法确定了只能操纵mysql
数据库,如果对oracle
、sql server
操作,则需要修改代码。直接使用new
创建堆对象使得程序过于依赖实现细节。
// EmployeeDAO1.cpp
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 IDBCommand {};
class IDBDataReader {};
// mysql支持
class SqlConnection : public IDBConnection {};
class SqlCommand : public IDBCommand {};
class SqlDataReader : public IDBDataReader {};
// oracle支持
class OracleConnection : public IDBConnection {};
class OracleCommand : public IDBCommand {};
class OracleDataReader : public IDBDataReader {};
class EmployeeDAO{
public:
vector<EmployeeDO> GetEmployees(){
IDBConnection* connection =
new SqlConnection();
connection->ConnectionString = "...";
IDBCommand* command =
new SqlCommand();
command->CommandText="...";
command->SetConnection(connection);
IDBDataReader* reader = command->ExecuteReader();
while (reader->Read()){ }
}
};
第一步优化实现了面向接口,但是抽象接口等号之后的new
属于实现细节,违反“依赖倒置原则”。根据Factory Method模式设计思想,我们可以使用工厂方法来封装对象创建细节。
// EmployeeDAO2.cpp
//数据库访问有关的基类
class IDBConnection { };
class IDBCommand { };
class IDBDataReader { };
// IDBConnection 对象工厂
class IDBConnectionFactory{
public:
virtual IDBConnection* CreateDBConnection()=0;
};
// IDBCommand 对象工厂
class IDBCommandFactory{
public:
virtual IDBCommand* CreateDBCommand()=0;
};
// IDBDataReader 对象工厂
class IDBDataReaderFactory{
public:
virtual IDBDataReader* CreateDBDataReader()=0;
};
//支持SQL Server
class SqlConnection : public IDBConnection { };
class SqlConnectionFactory : public IDBConnectionFactory {
public:
virtual SqlConnection* CreateDBConnection() {
return new SqlConnection();
}
};
class SqlCommand : public IDBCommand { };
class SqlCommandFactory : public IDBCommandFactory {
public:
virtual SqlCommand* CreateDBCommand() {
return new SqlCommand();
}
};
class SqlDataReader : public IDBDataReader { };
class SqlDataReaderFactory : public IDBDataReaderFactory {
virtual SqlDataReader* CreateDBDataReader() {
return new SqlDataReader();
}
};
//支持Oracle
class OracleConnection : public IDBConnection { };
class OracleCommand : public IDBCommand { };
class OracleDataReader : public IDBDataReader { };
class EmployeeDAO{
IDBConnectionFactory* dbConnectionFactory;
IDBCommandFactory* dbCommandFactory;
IDBDataReaderFactory* dbDataReaderFactory;
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()) { }
}
};
通过类图可以清楚的观察工厂方法解决问题的思路,每一种抽象工厂可以解决相同基类的子类对象创建,数据库操作系列共有三个继承关系,所以需要三个工厂。因此在EmployeeDAO
类中维护了三个工厂对象指针。
直到现在貌似已经解决了问题,但是如果细心观察可以发现,其实数据库的链接、命令创建、结果获取属于同一个系列,在创建对象时,必须保证该系列都是针对同一个数据库,而不能链接使用SqlConnection
,读取使用OracleDataReader
,但上述代码没有体现这种约束关系(关联性),这样会导致代码紊乱。
为了解决这个问题,将一个系列的对象创建封装在同一个抽象工厂当中,即家族工厂,每一个工厂可以创建一个系列中的任意一种类型对象,而不是和工厂方法一样只能创建同继承关系下的一类对象。
// EmployeeDAO3.cpp
//数据库访问有关的基类
class IDBConnection{};
class IDBCommand{};
class IDataReader{};
//支持SQL Server
class SqlConnection: public IDBConnection{};
class SqlCommand: public IDBCommand{};
class SqlDataReader: public IDataReader{};
// 抽象工厂
class IDBFactory{
public:
virtual IDBConnection* CreateDBConnection()=0;
virtual IDBCommand* CreateDBCommand()=0;
virtual IDataReader* CreateDataReader()=0;
};
class SqlDBFactory : public IDBFactory{
public:
virtual IDBConnection* CreateDBConnection() {
return new SqlConnection();
}
virtual IDBCommand* CreateDBCommand() {
return new SqlCommand();
}
virtual IDataReader* CreateDataReader() {
return new SqlDAtaReader();
}
};
//支持Oracle
class OracleConnection: public IDBConnection{};
class OracleCommand: public IDBCommand{};
class OracleDataReader: public IDataReader{};
class OracleDBFactory : public IDBFactory{
public:
virtual IDBConnection* CreateDBConnection() {
return new OracleConnection();
}
virtual IDBCommand* CreateDBCommand() {
return new OracleCommand();
}
virtual IDataReader* CreateDataReader() {
return new OracleDataReader();
}
};
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()) { }
}
};
通过代码解释动机:
- “一系列相互依赖的对象”指的就是有关联性的对象,比如
IDBConnection
、IDBCommend
、IDBDataReader
三个对象必须同时处理同一个数据库。 - “更多系列创建工作”指的是,软件可能操作
sql server
、oracle
多种不同的数据库,即为多系列。
4. 模式定义
提供一个接口,让该接口负责创建一系列“相关或者相互依赖的对象”,无需指定它们具体的类。
对比Factory Method与Abstract Factory:
- Factory Method为工厂方法,主要应用在同一个基类下子类对象的创建工作,需求的变化点为“单个子类类型变化”。可总结为一段代码
Abstract *abs = abstractFactory.Create()
,其中Abstract
代表基类,abstractFactory
为抽象工厂对象,其子类实现具体的子类对象创建工作。 - Abstract Factory为抽象工厂,主要应用在一系列具有依赖或者关联关系对象的创建工作。和工厂方法不同的时,一个工厂可以通过不同方法创建同系列种的所有对象,这些对象具有依赖性或关联性,变化点为”不同系列”,比如
sql
系列、oracle
系列等。 - 如果抽象工厂中只有一个方法,那么就退化为了工厂方法。
5. 结构
绿色表示稳定,红色表示变化。其中AbstractFactory
相当于IDBFactory
,AbstractProductA/B
相当于IDBConnection
、IDBCommand
、IDBDataReader
,ConcreteFactory1/2
相当于SqlDBFactory
、OracleDBFactory
,ProductA1/2
相当于SqlConnection
与OracleConnection
,ProductB1/2
相当于SqlCommand
、OracleCommand
。
6. 要点总结
- 如果没有应对“多系列对象构建”的需求变化,则没有必要使用Abstract Factory模式,这时候使用简单的工厂完全可以。
- “系列对象”指的是在某一特定系列下的对象之间有相互依赖、或作用的关系。不同系列的对象之间不能相互依赖。
- Abstract Factory模式主要在于应对“新系列”的需求变动。其缺点在于难以应对“新对象”的需求变动。静态部分存在的问题就是该设计模式的缺点,正是由于工厂的稳定性,才导致不能增加新对象。这也提醒我们每个设计模式都有优缺点,不应该在完全变化或者完全稳定情况下使用设计模式,而要在稳定(保证稳定,不能发生频繁变化)中存在变化的前提下使用设计模式才会起到效果。