第5章 创建型模式—抽象工厂模式
1. 抽象工厂的定义
(1)提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类
①只需要知道创建一系列对象的接口,而无需知道具体使用的是哪一个实现
②这一系列对象是相关或相互依赖的,也就是说既要创建对象,还要约束它们之间的关系。
③一系列对象是构建新对象所需要的组成部分,并且对象之间相互有约束。如电脑由CPU和主板等组成,但CPU的针脚数和主板提供的插口必须是匹配的,否则无法组装。
(2)产品族和产品等级
①产品族:在抽象工厂模式中,产品族是指由同一个工厂生产的,位于不同产品等级结构,功能相关联的产品组成的家族。如,AMD的主板、芯片组、CPU组成一个家族,Intel的主板、芯片组、CPU组成一个家族。
②产品等级结构:产品等级结构即产品的继承结构。如抽象电视机与具体品牌的电视机之间构成了一个产品等级结构。如,AMD和Intel这两个家族都来自于三个产品等级:主板、芯片组、CPU。一个等级结构是由相同的结构的产品组成。
(2)抽象工厂模式的结构图
①Abstract Factory:抽象工厂,定义创建一系列产品对象的操作接口
②Concrete Factory:具体的工厂,实现抽象工厂定义的方法,具体实现一系列对象的创建。
③Abstract Product:定义一类产品对象的接口
④Concrete Product:具体的产品实现对象,通常在具体工厂里面,会选择具体的产品实现对象,来创建符合抽象工厂类中定义的相应的接口类型对象。
⑤Client:客户端,主要使用抽象工厂来获取一系列所需要的产品对象,然后面向这些产品对象的接口编程。
2. 思考抽象工厂模式
(1)抽象工厂模式的本质:选择产品簇的实现,即每个具体工厂创建的是一系列的产品。
①工厂方法针对的是单个产品对象的创建,本质是选择单个产品的实现。而抽象工厂着重的是创建一个产品簇的实现,在抽象工厂类里定义的接口通常是有联系的,它们都是产品的某一部分或者是相互依赖的。注意,这些接口不是任意堆砌的,而是一系列相关或相互依赖的方法。
②如果抽象工厂里面只定义一个方法,直接创建产品,那就退化为工厂方法了。
(2)切换产品簇:只要提供不同的抽象工厂实现,产品簇就可以作为一个整体被切换。
【编程实验】电脑装机方案
//创建型模式:抽象工厂模式
//电脑组装
#include <stdio.h>
////////////////////////////////////////////产品(等级)////////////////////////////
//产品等级1:
//1、抽象CPU类
class CPUApi
{
public:
virtual void calculate() = 0;
};
//1.1 AMD CPU(具体CPU产品类)
class AMDCPU : public CPUApi
{
private:
int pints;
public:
AMDCPU(int pints){this->pints = pints;}
void calculate()
{
printf("now in AMD CPU, pints = %d\n",pints);
}
};
//1.2 Intel CPU(具体CPU产品类)
class IntelCPU : public CPUApi
{
private:
int pints;
public:
IntelCPU(int pints){this->pints = pints;}
void calculate()
{
printf("now in Intel CPU, pints = %d\n",pints);
}
};
//产品等级2
//2.抽象主板类
class MainboardApi
{
public:
virtual void installCPU() = 0;
};
//2.1 技嘉主板
class GAMainboard : public MainboardApi
{
private:
int cpuHoles; //CPU插槽的孔数
public:
GAMainboard(int cpuHoles){this->cpuHoles = cpuHoles;}
void installCPU()
{
printf("now in GAMainboard, cpuHoles = %d\n", cpuHoles);
}
};
//2.2 微星主板
class MSIMainboard : public MainboardApi
{
private:
int cpuHoles; //CPU插槽的孔数
public:
MSIMainboard(int cpuHoles){this->cpuHoles = cpuHoles;}
void installCPU()
{
printf("now in MSIMainboard, cpuHoles = %d\n", cpuHoles);
}
};
////////////////////////////////////////////工厂类////////////////////////////
//3.抽象工厂接口,声明创建抽象产品对象的操作
class AbstractFactory
{
public:
//创建CPU对象
virtual CPUApi* createCPUApi() = 0;
//创建主板对象
virtual MainboardApi* createMainboardApi() = 0;
};
//3.1具体工厂(装机方案1):Intel的CPU+技嘉主板
// 这里创建的CPU和主板对象是能匹配的,即有约束关系
class Schema1 : public AbstractFactory
{
public:
//CPU
CPUApi* createCPUApi()
{
return new IntelCPU(1156);
}
//主板对象
MainboardApi* createMainboardApi()
{
return new GAMainboard(1156);
}
};
//3.2具体工厂(装机方案2):AMD的CPU+微星主板
// 这里创建的CPU和主板对象是能匹配的,即有约束关系
class Schema2 : public AbstractFactory
{
public:
//CPU
CPUApi* createCPUApi()
{
return new AMDCPU(939);
}
//主板对象
MainboardApi* createMainboardApi()
{
return new MSIMainboard(939);
}
};
int main()
{
//客户端调用例子
//工厂
AbstractFactory* af = new Schema1(); //可以整体换装机方案
//CPU
CPUApi* cpu = af->createCPUApi();
//主板
MainboardApi* mainboard = af->createMainboardApi();
//测试一下配件是否正常
cpu->calculate();
mainboard->installCPU();
delete cpu;
delete mainboard;
delete af;
return 0;
}
3. 抽象工厂模式的优缺点
(1)优点
①封装性:抽象工厂模式隔离了具体类的生成,使得客户并不需要知道什么被创建。由于这种隔离,更换一个具体工厂就变得相对容易。所有的具体工厂都实现了抽象工厂中定义的那些公共接口,因此只需改变具体工厂的实例,就可以在某种程度上改变整个软件系统的行为。另外,应用抽象工厂模式可以实现高内聚低耦合的设计目的,因此抽象工厂模式得到了广泛的应用。
②当一个产品族中的多个对象被设计成一起工作时,它能够保证客户端始终只使用同一个产品族中的对象(即产品对象是有约束的,不可以随心所欲的创建对象)。这对一些需要根据当前环境来决定其行为的软件系统来说,是一种非常实用的设计模式。
③增加新的具体工厂和产品族很方便,无须修改已有系统,符合“开闭原则”
(2)缺点
①在添加新的产品对象时,难以扩展抽象工厂来生产新种类的产品,这是因为在抽象工厂角色中规定了所有可能被创建的产品集合,要支持新种类的产品就意味着要对该接口进行扩展,而这将涉及到对抽象工厂角色及其所有子类的修改,显然会带来较大的不便。
②开闭原则的倾斜性(增加新的工厂和产品族容易,增加新的产品等级结构麻烦)。
4. 抽象工厂模式的使用场景
(1)一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节,这对于所有形态的工厂模式都是重要的。
(2)这个系统的产品有多于一个的产品族,而系统只消费其中某一族的产品。
(3)同属于同一个产品族的产品是在一起使用的,这一约束必须在系统的设计中体现出来。说的更明白一点,就是一个继承体系中,如果存在着多个等级结构(即存在着多个抽象类),并且分属各个等级结构中的实现类之间存在着一定的关联或者约束(比如:Intel主板必须使用Intel CPU、Intel芯片组),就可以使用抽象工厂模式。假如各个等级结构中的实现类之间不存在关联或约束,则使用多个独立的工厂来对产品进行创建,则更合适一点。
5. 抽象工厂模式和DAO
(1)数据访问对象(Data Access Object,DAO):用于解决访问数据对象所面临的一系列问题。
①数据源不同,如本地数据源和远程服务器上的数据源
②存储类型不同(如关系型数据库(RDBMS)、XML、纯文件等。
③访问方式不同,比如ODBC、ADO
④供应商不同,如Oracle、DB2、SqlServer、MySQL等等。
⑤版本不同,如关系型数据库,不同版本,实现功能是有差异的。就算是对标准的SQL的支持,也是有差异的。
(2)DAO和抽象工厂的关系
①DAO模式最常见的实现策略是工厂的策略,而且多是通过抽象工厂模式来实现。
②在使用抽象工厂模式时,也可以结合工厂方法模式。
(3)DAO访问方式的工厂实现策略(以订单处理为例)
①订单通常分为主记录(主表)和明细记录(子表)
②当业务对象需要操作订单的主表时,一般也需要操作子表
③如果业务简单,且对数据的操作是固定的,即不管订单业务如何变化,底层数据存储都是一样的,那么这时可以采用工厂方法模式。如果底层存储不固定(如存在数据库,或Xml文件),则一般采用抽象工厂模式。
【编程实例】利用抽象工厂模式将业务对象存储在不同的数据仓库
//创建型模式:抽象工厂模式
//数据访问对象策略的抽象工厂实现
#include <stdio.h>
//以下的所说的数据仓库可以是关系型系数据库或Xml
//产品对象的接口,就是订单主、子记录的DAO定义
//订单主记录的DAO定义:(提供保存业务对象到数据仓库的功能)
class COrderMainDAO
{
public:
virtual void saveOrderMain() = 0;
};
//订单子记录的DAO定义(提供保存业务对象到数据仓库的功能)
class COrderDetailDAO
{
public:
virtual void saveOrderDetail() = 0;
};
//Rdb实现:将业务对象保存到关系型数据库
class CRdbMainDAOImpl : public COrderMainDAO
{
public:
void saveOrderMain()
{
printf("now in RdbMainDAOImpl saveOrderMain\n");
}
};
//Rdb实现:将业务对象保存到关系型数据库
class CRdbDetailDAOImpl : public COrderDetailDAO
{
public:
void saveOrderDetail()
{
printf("now in RdbDetailDAOImpl saveOrderDetail\n");
}
};
//Xml实现:将业务对象保存到xml文件
class CXmlMainDAOImpl : public COrderMainDAO
{
public:
void saveOrderMain()
{
printf("now in XmlMainDAOImpl saveOrderMain\n");
}
};
//Xml实现:将业务对象保存到xml文件
class CXmlDetailDAOImpl : public COrderDetailDAO
{
public:
void saveOrderDetail()
{
printf("now in XmlDetailDAOImpl saveOrderDetail\n");
}
};
//抽象工厂,创建订单主、子记录对应的DAO对象
class CDAOFactory
{
public:
//订单主记录对应的DAO对象
virtual COrderMainDAO* createOrderMainDAO() = 0;
//订单子记录对应的DAO对象
virtual COrderDetailDAO* createOrderDetailDAO() = 0;
};
//具体工厂
//关系型数据库的实现方式
class CRdbDAOFactory : public CDAOFactory
{
public:
COrderMainDAO* createOrderMainDAO()
{
return new CRdbMainDAOImpl();
}
COrderDetailDAO* createOrderDetailDAO()
{
return new CRdbDetailDAOImpl();
}
};
//Xml的实现方式
class CXmlDAOFactory : public CDAOFactory
{
public:
COrderMainDAO* createOrderMainDAO()
{
return new CXmlMainDAOImpl();
}
COrderDetailDAO* createOrderDetailDAO()
{
return new CXmlDetailDAOImpl();
}
};
int main()
{
//客户端调用例子
//创建DAO的抽象工厂
//CDAOFactory* df = new CRdbDAOFactory();
CDAOFactory* df = new CXmlDAOFactory();
//通过抽象工厂来获取需要的DAO接口
COrderMainDAO* mainDAO = df->createOrderMainDAO();
COrderDetailDAO* detailDAO = df->createOrderDetailDAO();
//调用DAO来完成数据存储的功能
mainDAO->saveOrderMain();
detailDAO->saveOrderDetail();
delete mainDAO;
delete detailDAO;
delete df;
return 0;
}