9.抽象工厂(Abstract Factory)

本文讲述了对象创建模式中的抽象工厂模式,介绍了抽象工厂模式动机、定义、结构、代码实例,最后进行了总结。
再次感谢GeekBand的李建忠老师、GOF等前辈

1. “对象创建”模式

  • 通过“对象创建” 模式绕开new,来避免对象创建(new)过程中所导致的紧耦合(依赖具体类),从而支持对象创建的稳定。它是接口抽象之后的第一步工作。
  • 典型模式
    • Factory Method
    • Abstract Factory 抽象工厂
    • Prototype
    • Builder

2. 动机

  • 在软件系统中,经常面临着“一系列相互依赖的对象[通过代码详细解释]”的创建工作;同时,由于需求的变化,往往存在更多系列对象的创建工作[通过代码详细解释]
  • 如何应对这种变化?如何绕过常规的对象创建方法(new),提供一种“封装机制”来避免客户程序和这种“多系列具体对象创建工作”的紧耦合?

3. 代码实例

代码EmployeeDAO.cpp实现的是mysql数据库访问,其中SqlConnection用于与mysql建立连接,SqlCommand用于记录sql命令,SqlDataReader用于读取查询结果。但程序明显违背了面向接口编程与依赖倒置原则,如下程序做法确定了只能操纵mysql数据库,如果对oraclesql 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()) { }
    }
};

在这里插入图片描述
通过代码解释动机:

  • 一系列相互依赖的对象”指的就是有关联性的对象,比如IDBConnectionIDBCommendIDBDataReader三个对象必须同时处理同一个数据库。
  • 更多系列创建工作”指的是,软件可能操作sql serveroracle多种不同的数据库,即为多系列。

4. 模式定义

提供一个接口,让该接口负责创建一系列“相关或者相互依赖的对象”,无需指定它们具体的类。

对比Factory Method与Abstract Factory:

  • Factory Method为工厂方法,主要应用在同一个基类下子类对象的创建工作,需求的变化点为“单个子类类型变化”。可总结为一段代码Abstract *abs = abstractFactory.Create(),其中Abstract代表基类,abstractFactory为抽象工厂对象,其子类实现具体的子类对象创建工作。
  • Abstract Factory为抽象工厂,主要应用在一系列具有依赖或者关联关系对象的创建工作。和工厂方法不同的时,一个工厂可以通过不同方法创建同系列种的所有对象,这些对象具有依赖性或关联性,变化点为”不同系列”,比如sql系列、oracle系列等。
  • 如果抽象工厂中只有一个方法,那么就退化为了工厂方法。

5. 结构

在这里插入图片描述
绿色表示稳定,红色表示变化。其中AbstractFactory相当于IDBFactoryAbstractProductA/B相当于IDBConnectionIDBCommandIDBDataReaderConcreteFactory1/2相当于SqlDBFactoryOracleDBFactoryProductA1/2相当于SqlConnectionOracleConnectionProductB1/2相当于SqlCommandOracleCommand

6. 要点总结

  • 如果没有应对“多系列对象构建”的需求变化,则没有必要使用Abstract Factory模式,这时候使用简单的工厂完全可以。
  • “系列对象”指的是在某一特定系列下的对象之间有相互依赖、或作用的关系。不同系列的对象之间不能相互依赖。
  • Abstract Factory模式主要在于应对“新系列”的需求变动。其缺点在于难以应对“新对象”的需求变动。静态部分存在的问题就是该设计模式的缺点,正是由于工厂的稳定性,才导致不能增加新对象。这也提醒我们每个设计模式都有优缺点,不应该在完全变化或者完全稳定情况下使用设计模式,而要在稳定(保证稳定,不能发生频繁变化)中存在变化的前提下使用设计模式才会起到效果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值