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 ",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 ",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 ", 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 ", 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 "); } }; //Rdb实现:将业务对象保存到关系型数据库 class CRdbDetailDAOImpl : public COrderDetailDAO { public: void saveOrderDetail() { printf("now in RdbDetailDAOImpl saveOrderDetail "); } }; //Xml实现:将业务对象保存到xml文件 class CXmlMainDAOImpl : public COrderMainDAO { public: void saveOrderMain() { printf("now in XmlMainDAOImpl saveOrderMain "); } }; //Xml实现:将业务对象保存到xml文件 class CXmlDetailDAOImpl : public COrderDetailDAO { public: void saveOrderDetail() { printf("now in XmlDetailDAOImpl saveOrderDetail "); } }; //抽象工厂,创建订单主、子记录对应的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; }