【前言】在软件系统中,经常面临着“一系列相互依赖的对象”的创建工作;同时,由于需求的变化,往往存在更多系列对象的创建工作。
引言
什么叫“一系列相互依赖的对象”?
假设我们目前使用的是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{};/////支持Oracleclass 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 Serverclass SqlConnection: public IDBConnection{};class SqlConnectionFactory:public IDBConnectionFactory{};class SqlCommand: public IDBCommand{};class SqlCommandFactory:public IDBCommandFactory{};class SqlDataReader: public IDataReader{};class SqlDataReaderFactory:public IDataReaderFactory{};/////支持Oracleclass 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 Serverclass 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;};//支持Oracleclass 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可能会贴切一点。
- 任务模式都会有缺点,我们所说的一个模式的稳定部分,就是他的缺点。如果你假设稳定部分将来会变化,那个模式就不使用了。可能会有其他模式可以解决这个变化。
- 如果你极端的假设任何一个地方都有变化,那么没有任何一个模式或几个模式合起来,能够解决你的问题。所以说不能够推到一个极端。极端就是所有内容都会变化。
- 如果你没有那么多变化,那就没有必要使用模式,直接写就完事了。设计模式是解决稳定中有变化的场景。
- 软件中很难走向两个极端:所有地方都有变化;所有地方都是稳定的。所有地方都变化,没有设计模式能够解决这种问题。所有地方都稳定,就没有必要用模式了。
