【前言】在软件系统中,经常面临着“一系列相互依赖的对象”的创建工作;同时,由于需求的变化,往往存在更多系列对象的创建工作。
引言
什么叫“一系列相互依赖的对象”?
假设我们目前使用的是SQL Server数据库,要实现一个对SQL Server数据库的访问层。那么,紧接着定义了数据库连接对象、数据库命令对象、数据库的DataReader对象,而这些对象都是与SQL Server相关。
//数据访问层
class EmployeeDAO{
public:
//假设此为SQL Server的数据库
//创建一系列的对象
vector<EmployeeDO> GetEmployees(){
//数据库的连接
SqlConnection* connection = new SqlConnection();
connection->ConnectionString = "...";
//数据库的命令对象
SqlCommand* command = new SqlCommand();
command->CommandText="...";
command->SetConnection(connection);
//数据库的DataReader对象
SqlDataReader* reader = command->ExecuteReader();
while (reader->Read()){
}
}
};
【变化】但是如果你的客户需要让你更改数据库,上面所创建的数据类型就可能要发生变化。
所以,使用new是不合适的。因为new写死了数据类型,如果变化一个数据库,这个类型就不适用于其他数据库。
面向接口编程
定义一些虚基类,用面向接口编程的思想来应对这种变化。
/////数据库访问有关的基类
class IDBConnection{
};
class IDBCommand{
};
class IDataReader{
};
/////支持SQL Server的数据结构
class SqlConnection: public IDBConnection{
};
class SqlCommand: public IDBCommand{
};
class SqlDataReader: public IDataReader{
};
/////支持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 = new SqlConnection(); //?
connection->ConnectionString("...");
IDBCommand* command = new SqlCommand(); //?
command->CommandText("...");
command->SetConnection(connection);
IDBDataReader* reader = command->ExecuteReader();
while (reader->Read()){
}
}
};
虽然,对象的类型换成了抽象基类。但是,第33行、36行怎么办?new后面是一个具体的类型。此时,就想到了工厂设计模式,定义一些工厂来解决这些细节依赖,用类的方法来代替new。
/////数据库访问有关的基类,和对应的工厂
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 OracleConnectionFactory:public IDBConnectionFactory{
};
class OracleCommand: public IDBCommand{
};
class OracleCommandFactory: public IDBCommandFactory{
};
class OracleDataReader: public IDataReader{
};
class OracleDataReaderFactory: public IDataReaderFactory{
};
/////数据访问层
class EmployeeDAO{
//这三个变量就要在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()){
}
}
};
关联性(抽象工厂)
【关联性】IDBConnection、IDBCommand、IDBDataReader之间具有关联性
- 如果用的是SQL Server,三者都要是支持SQL Server的类型
- 并且SQL Server的IDBCommand中要设置SQL Server的IDBConnection,IDBDataReader也是一样
- 这就是三个类型之间的关联性,它们并不是独立的。三者是要属于同一个族,而这个族取决于数据库
【问题】如果出现一个人,他传入的IDBConnectionFactory
是SQL Server的DBConnectionFactory
,IDBCommandFactory
是MySQL的DBCommandFactory
,会有什么问题?
- 在两者设置关联性的时候,就会报错(
command->SetConnection(connection);
)
【解决思路】那把IDBConnectionFactory
、IDBCommandFactory
、IDataReaderFactory
的三个方法放在一起,放在一个工厂里,可不可以有所改善?
//数据库访问有关的基类
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 OracleDBFactory:public IDBFactory{
public:
virtual IDBConnection* CreateDBConnection()=0;
virtual IDBCommand* CreateDBCommand()=0;
virtual IDataReader* CreateDataReader()=0;
};
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()){
}
}
};
高内聚、松耦合。如果有相关性的就能放在一个工厂内部。
如此,就保证了三者的关联性,保证了它们是属于一个族的。
其实,这个名字取的不好,“抽象工厂”。叫FamilyFactory可能会贴切一点。
动机
在软件系统中,经常面临着“一系列相互依赖的对象”的创建工作;同时,由于需求的变化,往往存在更多系列对象的创建工作。
- SQLServer是一个系列;MySQL是一个系列,Oracle是一个系列
- 如果应对这些变化呢?就是绕开new
如果应对这种变化?如果绕过常规的对象创建方法(new),提供一种“封装机制”来避免客户程序和这种“多系列具体对象创建工作”的紧耦合。
模式定义
提供一个接口,让该接口负责创建一系列“相关或者相互依赖的对象”,无需指定它们具体的类。——《设计模式》GoF
结构
要点总结
- 如果没有应用“多系列对象构建”的需求变化,则没有必要使用AbstractFactory模式,这时候使用简单的工厂完全可以。
- “系列对象”指的是在某一特定系列下的对象之间有相互依赖、或作用的关系。不同系列的对象之间不能相互依赖
- AbstractFactory模式主要在于应对“新系列”的需求变动。其缺点在于难以应对“新对象”的需求变动。
注释:
- 其实,这个名字取的不好,“抽象工厂”。叫FamilyFactory可能会贴切一点。
- 任务模式都会有缺点,我们所说的一个模式的稳定部分,就是他的缺点。如果你假设稳定部分将来会变化,那个模式就不使用了。可能会有其他模式可以解决这个变化。
- 如果你极端的假设任何一个地方都有变化,那么没有任何一个模式或几个模式合起来,能够解决你的问题。所以说不能够推到一个极端。极端就是所有内容都会变化。
- 如果你没有那么多变化,那就没有必要使用模式,直接写就完事了。设计模式是解决稳定中有变化的场景。
- 软件中很难走向两个极端:所有地方都有变化;所有地方都是稳定的。所有地方都变化,没有设计模式能够解决这种问题。所有地方都稳定,就没有必要用模式了。