#0 系列目录#数据库
#1 场景问题# ##1.1 选择组装电脑的配件## 举个生活中常见的例子——组装电脑,咱们在组装电脑的时候,一般须要选择一系列的配件,好比:CPU、硬盘、内存、主板、电源、机箱等等。为了使讨论简单点,只考虑选择CPU和主板的问题。编程
事实上,咱们在选择CPU的时候,面临一系列的问题,好比:品牌、型号、针脚数目、主频等问题,只有把这些都肯定下来,才能肯定具体的CPU。一样,在选择主板的时候,也有一系列的问题,好比:品牌、芯片组、集成芯片、总线频率等问题,也只有这些都肯定了,才能肯定具体的主板。设计模式
选择不一样的CPU和主板,是每一个客户去组装电脑的时候,向装机公司提出的要求,也就是咱们每一个人本身拟定的装机方案。缓存
在最终肯定这个装机方案以前,还须要总体考虑各个配件之间的兼容性,好比:CPU和主板,若是CPU针脚数和主板提供的CPU插口不兼容,是没法组装的。也就是说,装机方案是有总体性的,里面选择的各个配件之间是有关联的
。安全
对于装机工程师而言,他只知道组装一台电脑,须要相应的配件,可是具体使用什么样的配件,还得由客户说了算。也就是说装机工程师只是负责组装,而客户负责选择装配所须要的具体的配件。所以,当装机工程师为不一样的客户组装电脑时,只须要按照客户的装机方案,去获取相应的配件,而后组装便可。服务器
如今须要使用程序来把这个装机的过程,尤为是选择组装电脑配件的过程实现出来,该如何实现呢?框架
##1.2 不用模式的解决方案## 考虑客户的功能,须要选择本身须要的CPU和主板,而后告诉装机工程师本身的选择,接下来就等着装机工程师组装机器了。测试
对装机工程师而言,只是知道CPU和主板的接口,而不知道具体实现,很明显能够用上简单工厂或工厂方法模式,为了简单,这里选用简单工厂吧。客户告诉装机工程师本身的选择,而后装机工程师会经过相应的工厂去获取相应的实例对象。ui
/** * CPU的接口 */ public interface CPUApi { /** * 示意方法,CPU具备运算的功能 */ public void calculate(); }
再看看主板的接口定义,示例代码以下:this
/** * Mainboard的接口 */ public interface MainboardApi { public void installCPU(); }
/** * Intel的CPU实现 */ public class IntelCPU implements CPUApi{ /** * CPU的针脚数目 */ private int pins = 0; /** * 构造方法,传入CPU的针脚数目 * @param pins CPU的针脚数目 */ public IntelCPU(int pins){ this.pins = pins; } public void calculate() { System.out.println("now in Intel CPU,pins="+pins); } }
再看看AMD的CPU实现,示例代码以下:
/** * AMD的CPU实现 */ public class AMDCPU implements CPUApi{ /** * CPU的针脚数目 */ private int pins = 0; /** * 构造方法,传入CPU的针脚数目 * @param pins CPU的针脚数目 */ public AMDCPU(int pins){ this.pins = pins; } public void calculate() { System.out.println("now in AMD CPU,pins="+pins); } }
/** * 技嘉的主板 */ public class GAMainboard implements MainboardApi { /** * CPU插槽的孔数 */ private int cpuHoles = 0; /** * 构造方法,传入CPU插槽的孔数 * @param cpuHoles CPU插槽的孔数 */ public GAMainboard(int cpuHoles){ this.cpuHoles = cpuHoles; } public void installCPU() { System.out.println("now in GAMainboard,cpuHoles=" + cpuHoles); } }
再看看微星的主板实现,示例代码以下:
/** * 微星的主板 */ public class MSIMainboard implements MainboardApi{ /** * CPU插槽的孔数 */ private int cpuHoles = 0; /** * 构造方法,传入CPU插槽的孔数 * @param cpuHoles CPU插槽的孔数 */ public MSIMainboard(int cpuHoles){ this.cpuHoles = cpuHoles; } public void installCPU() { System.out.println("now in MSIMainboard,cpuHoles=" + cpuHoles); } }
/** * 建立CPU的简单工厂 */ public class CPUFactory { /** * 建立CPU接口对象的方法 * @param type 选择CPU类型的参数 * @return CPU接口对象的方法 */ public static CPUApi createCPUApi(int type){ CPUApi cpu = null; //根据参数来选择并建立相应的CPU对象 if(type==1){ cpu = new IntelCPU(1156); }else if(type==2){ cpu = new AMDCPU(939); } return cpu; } }
再看看建立主板的工厂实现,示例代码以下:
/** * 建立主板的简单工厂 */ public class MainboardFactory { /** * 建立主板接口对象的方法 * @param type 选择主板类型的参数 * @return 主板接口对象的方法 */ public static MainboardApi createMainboardApi(int type){ MainboardApi mainboard = null; //根据参数来选择并建立相应的主板对象 if(type==1){ mainboard = new GAMainboard(1156); }else if(type==2){ mainboard = new MSIMainboard(939); } return mainboard; } }
/** * 装机工程师的类 */ public class ComputerEngineer { /** * 定义组装机器须要的CPU */ private CPUApi cpu= null; /** * 定义组装机器须要的主板 */ private MainboardApi mainboard = null; /** * 装机过程 * @param cpuType 客户选择所需CPU的类型 * @param mainboardType 客户选择所需主板的类型 */ public void makeComputer(int cpuType,int mainboardType){ //1:首先准备好装机所须要的配件 prepareHardwares(cpuType,mainboardType); //2:组装机器 //3:测试机器 //4:交付客户 } /** * 准备装机所须要的配件 * @param cpuType 客户选择所需CPU的类型 * @param mainboardType 客户选择所需主板的类型 */ private void prepareHardwares(int cpuType,int mainboardType){ //这里要去准备CPU和主板的具体实现,为了示例简单,这里只准备这两个 //但是,装机工程师并不知道如何去建立,怎么办呢? //直接找相应的工厂获取 this.cpu = CPUFactory.createCPUApi(cpuType); this.mainboard = MainboardFactory.createMainboardApi(mainboardType); //测试一下配件是否好用 this.cpu.calculate(); this.mainboard.installCPU(); } }
public class Client { public static void main(String[] args) { //建立装机工程师对象 ComputerEngineer engineer = new ComputerEngineer(); //告诉装机工程师本身选择的配件,让装机工程师组装电脑 engineer.makeComputer(1,1); } }
##1.3 有何问题## 看了上面的实现,会感受到很简单嘛,经过使用简单工厂来获取须要的CPU和主板对象,而后就能够组装电脑了。有何问题呢?
虽然上面的实现,经过简单工厂解决解决了:对于装机工程师,只知CPU和主板的接口,而不知道具体实现的问题。但还有一个问题没有解决,什么问题呢?那就是这些CPU对象和主板对象实际上是有关系的,是须要相互匹配的
。而在上面的实现中,并无维护这种关联关系,CPU和主板是由客户随意选择的。这是有问题的。
这就是没有维护配件之间的关系形成的。该怎么解决这个问题呢?
#2 解决方案# ##2.1 抽象工厂模式来解决## 用来解决上述问题的一个合理的解决方案就是抽象工厂模式。那么什么是抽象工厂模式呢?
仔细分析上面的问题,其实有两个问题点,一个是只知道所须要的一系列对象的接口,而不知具体实现,或者是不知道具体使用哪个实现;另一个是这一系列对象是相关或者相互依赖的。也就是说既要建立接口的对象,还要约束它们之间的关系。
有朋友可能会想,工厂方法模式或者是简单工厂,不就能够解决只知接口而不知实现的问题吗?怎么这些问题又冒出来了呢?
请注意,这里要解决的问题和工厂方法模式或简单工厂解决的问题是有很大不一样的,工厂方法模式或简单工厂关注的是单个产品对象的建立,好比建立CPU的工厂方法,它就只关心如何建立CPU的对象,而建立主板的工厂方法,就只关心如何建立主板对象。
这里要解决的问题是,要建立一系列的产品对象,并且这一系列对象是构建新的对象所须要的组成部分,也就是这一系列被建立的对象相互之间是有约束的。
**解决这个问题的一个解决方案就是抽象工厂模式。
**在这个模式里面,会定义一个抽象工厂,在里面虚拟的建立客户端须要的这一系列对象,所谓虚拟的就是定义建立这些对象的抽象方法,并不去真的实现,而后由具体的抽象工厂的子类来提供这一系列对象的建立。这样一来能够为同一个抽象工厂提供不少不一样的实现,那么建立的这一系列对象也就不同了,也就是说,抽象工厂在这里起到一个约束的做用,并提供全部子类的一个统一外观,来让客户端使用。
##2.2 模式结构和说明## 抽象工厂模式结构如图所示:
AbstractFactory:抽象工厂,定义建立一系列产品对象的操做接口。
ConcreteFactory:具体的工厂,实现抽象工厂定义的方法,具体实现一系列产品对象的建立。
AbstractProduct:定义一类产品对象的接口。
ConcreteProduct:具体的产品实现对象,一般在具体工厂里面,会选择具体的产品实现对象,来建立符合抽象工厂定义的方法返回的产品类型的对象。
Client:客户端,主要使用抽象工厂来获取一系列所须要的产品对象,而后面向这些产品对象的接口编程,以实现须要的功能。
##2.3 抽象工厂模式示例代码##
/** * 抽象工厂的接口,声明建立抽象产品对象的操做 */ public interface AbstractFactory { /** * 示例方法,建立抽象产品A的对象 * @return 抽象产品A的对象 */ public AbstractProductA createProductA(); /** * 示例方法,建立抽象产品B的对象 * @return 抽象产品B的对象 */ public AbstractProductB createProductB(); }
/** * 抽象产品A的接口 */ public interface AbstractProductA { //定义抽象产品A相关的操做 } /** * 抽象产品B的接口 */ public interface AbstractProductB { //定义抽象产品B相关的操做 }
/** * 产品A的具体实现 */ public class ProductA1 implements AbstractProductA { //实现产品A的接口中定义的操做 } /** * 产品A的具体实现 */ public class ProductA2 implements AbstractProductA { //实现产品A的接口中定义的操做 } /** * 产品B的具体实现 */ public class ProductB1 implements AbstractProductB { //实现产品B的接口中定义的操做 } /** * 产品B的具体实现 */ public class ProductB2 implements AbstractProductB { //实现产品B的接口中定义的操做 }
/** * 具体的工厂实现对象,实现建立具体的产品对象的操做 */ public class ConcreteFactory1 implements AbstractFactory { public AbstractProductA createProductA() { return new ProductA1(); } public AbstractProductB createProductB() { return new ProductB1(); } } /** * 具体的工厂实现对象,实现建立具体的产品对象的操做 */ public class ConcreteFactory2 implements AbstractFactory { public AbstractProductA createProductA() { return new ProductA2(); } public AbstractProductB createProductB() { return new ProductB2(); } }
public class Client { public static void main(String[] args) { //建立抽象工厂对象 AbstractFactory af = new ConcreteFactory1(); //经过抽象工厂来获取一系列的对象,如产品A和产品B af.createProductA(); af.createProductB(); } }
##2.4 使用抽象工厂模式重写示例## 要使用抽象工厂模式来重写示例,先来看看如何使用抽象工厂模式来解决前面提出的问题。
装机工程师要组装电脑对象,须要一系列的产品对象,好比CPU、主板等,因而建立一个抽象工厂给装机工程师使用,在这个抽象工厂里面定义抽象的建立CPU和主板的方法,这个抽象工厂就至关于一个抽象的装机方案,在这个装机方案里面,各个配件是可以相互匹配的。
每一个装机的客户,会提出他们本身的具体装机方案,或者是选择已有的装机方案,至关于为抽象工厂提供了具体的子类,在这些具体的装机方案类里面,会建立具体的CPU和主板实现对象。
此时系统的结构如图所示:
虽说是重写示例,但并非前面写的都不要了,而是修改前面的示例,使它能更好的实现须要的功能。
前面示例实现的CPU接口和CPU实现对象,还有主板的接口和实现对象,都不须要变化,这里就不去赘述了。
前面示例中的建立CPU的简单工厂和建立主板的简单工厂,都再也不须要了,直接删除便可,这里也就不去管了。
看看新加入的抽象工厂的定义,示例代码以下:
/** * 抽象工厂的接口,声明建立抽象产品对象的操做 */ public interface AbstractFactory { /** * 建立CPU的对象 * @return CPU的对象 */ public CPUApi createCPUApi(); /** * 建立主板的对象 * @return 主板的对象 */ public MainboardApi createMainboardApi(); }
/** * 装机方案一:Intel 的CPU + 技嘉的主板 * 这里建立CPU和主板对象的时候,是对应的,能匹配上的 */ public class Schema1 implements AbstractFactory{ public CPUApi createCPUApi() { return new IntelCPU(1156); } public MainboardApi createMainboardApi() { return new GAMainboard(1156); } } /** * 装机方案二:AMD的CPU + 微星的主板 * 这里建立CPU和主板对象的时候,是对应的,能匹配上的 */ public class Schema2 implements AbstractFactory{ public CPUApi createCPUApi() { return new AMDCPU(939); } public MainboardApi createMainboardApi() { return new MSIMainboard(939); } }
装机工程师类跟前面的实现相比,主要的变化是:从客户端,再也不传入选择CPU和主板的参数,而是直接传入客户选择并建立好的装机方案对象。这样就避免了单独去选择CPU和主板,客户要选就是一套,就是一个系列
。示例代码以下:
/** * 装机工程师的类 */ public class ComputerEngineer { /** * 定义组装机器须要的CPU */ private CPUApi cpu= null; /** * 定义组装机器须要的主板 */ private MainboardApi mainboard = null; /** * 装机过程 * @param schema 客户选择的装机方案 */ public void makeComputer(AbstractFactory schema){ //1:首先准备好装机所须要的配件 prepareHardwares(schema); //2:组装机器 //3:测试机器 //4:交付客户 } /** * 准备装机所须要的配件 * @param schema 客户选择的装机方案 */ private void prepareHardwares(AbstractFactory schema){ //这里要去准备CPU和主板的具体实现,为了示例简单,这里只准备这两个 //但是,装机工程师并不知道如何去建立,怎么办呢? //使用抽象工厂来获取相应的接口对象 this.cpu = schema.createCPUApi(); this.mainboard = schema.createMainboardApi(); //测试一下配件是否好用 this.cpu.calculate(); this.mainboard.installCPU(); } }
public class Client { public static void main(String[] args) { //建立装机工程师对象 ComputerEngineer engineer = new ComputerEngineer(); //客户选择并建立须要使用的装机方案对象 AbstractFactory schema = new Schema1(); //告诉装机工程师本身选择的装机方案,让装机工程师组装电脑 engineer.makeComputer(schema); } }
如同前面的示例,定义了一个抽象工厂AbstractFactory,在里面定义了建立CPU和主板对象的接口的方法,可是在抽象工厂里面,并无指定具体的CPU和主板的实现,也就是无须指定它们具体的实现类。
CPU和主板是相关的对象,是构建电脑的一系列相关配件,这个抽象工厂就至关于一个装机方案,客户选择装机方案的时候,一选就是一套,CPU和主板是肯定好的,不让客户分开选择,这就避免了出现不匹配的错误。
#3 模式讲解# ##3.1 认识抽象工厂模式##
抽象工厂的功能是为一系列相关对象或相互依赖的对象建立一个接口,必定要注意,这个接口内的方法不是任意堆砌的,而是一系列相关或相互依赖的方法,好比上面例子中的CPU和主板,都是为了组装一台电脑的相关对象。
从某种意义上看,抽象工厂实际上是一个产品系列,或者是产品簇。
上面例子中的抽象工厂就能够当作是电脑簇,每一个不一样的装机方案,表明一种具体的电脑系列。
AbstractFactory在Java中一般实现成为接口,你们不要被名称误导了,觉得是实现成为抽象类,固然,若是须要为这个产品簇提供公共的功能,也不是不能够把AbstractFactory实现成为抽象类,但通常不这么作。
AbstractFactory定义了建立产品所须要的接口,具体的实现是在实现类里面,一般在实现类里面就须要选择多种更具体的实现,因此AbstractFactory定义的建立产品的方法能够当作是工厂方法,而这些工厂方法的具体实现就延迟到了具体的工厂里面
。也就是说使用工厂方法来实现抽象工厂。
因为抽象工厂定义的一系列对象,一般是相关或者相依赖的,这些产品对象就构成了一个产品簇,也就是抽象工厂定义了一个产品簇。这就带来很是大的灵活性,切换一个产品簇的时候,只要提供不一样的抽象工厂实现就行了,也就是说如今是以产品簇作为一个总体被切换。
##3.2 定义可扩展的工厂## 在前面的示例中,抽象工厂为每一种它能建立的产品对象都定义了相应的方法,好比建立CPU的方法和建立主板的方法等。
这种实现有一个麻烦,就是若是在产品簇中要新增长一种产品,好比如今要求抽象工厂除了可以建立CPU和主板外,还要可以建立内存对象,那么就须要在抽象工厂里面添加建立内存的这么一个方法。当抽象工厂一发生变化,全部的具体工厂实现都要发生变化,这很是的不灵活。
如今有一种相对灵活,可是不太安全的改进方式来解决这个问题,思路以下:抽象工厂里面不须要定义那么多方法,定义一个方法就能够了,给这个方法设置一个参数,经过这个参数来判断具体建立什么产品对象;因为只有一个方法,在返回类型上就不能是具体的某个产品类型了,只能是全部的产品对象都继承或者实现的这么一个类型,好比让全部的产品都实现某个接口,或者干脆使用Object类型。
仍是看看代码来体会一下,把前面那个示例改形成可扩展的工厂实现。
/** * 可扩展的抽象工厂的接口 */ public interface AbstractFactory { /** * 一个通用的建立产品对象的方法,为了简单,直接返回Object * 也能够为全部被建立的产品定义一个公共的接口 * @param type 具体建立的产品类型标识 * @return 建立出的产品对象 */ public Object createProduct(int type); }
这里要特别注意传入createProduct的参数所表明的含义,这个参数只是用来标识如今是在建立什么类型的产品
,好比标识如今是建立CPU仍是建立主板,通常这个type的含义到此就结束了,再也不进一步表示具体是什么样的CPU或具体什么样的主板,也就是说type再也不表示具体是建立Intel的CPU仍是建立AMD的CPU,这就是一个参数所表明的含义的深度的问题,要注意
。虽然也能够延伸参数的含义到具体的实现上,但这不是可扩展工厂这种设计方式的本意,通常也不这么作
。
CPU的接口和实现,主板的接口和实现跟前面的示例是同样的,就再也不示范了。CPU仍是分红Intel的CPU和AMD的CPU,主板仍是分红技嘉的主板和微星的主板。
下面来提供具体的工厂实现,也就是至关于之前的装机方案,先改造原来的方案一吧,如今的实现会有较大的变化,示例代码以下:
/** * 装机方案一:Intel 的CPU + 技嘉的主板 * 这里建立CPU和主板对象的时候,是对应的,能匹配上的 */ public class Schema1 implements AbstractFactory{ public Object createProduct(int type) { Object retObj = null; //type为1表示建立CPU,type为2表示建立主板 if(type==1){ retObj = new IntelCPU(1156); }else if(type==2){ retObj = new GAMainboard(1156); } return retObj; } } /** * 装机方案二:AMD的CPU + 微星的主板 * 这里建立CPU和主板对象的时候,是对应的,能匹配上的 */ public class Schema2 implements AbstractFactory{ public Object createProduct(int type) { Object retObj = null; //type为1表示建立CPU,type为2表示建立主板 if(type==1){ retObj = new AMDCPU(939); }else if(type==2){ retObj = new MSIMainboard(939); } return retObj; } }
public class ComputerEngineer { private CPUApi cpu= null; private MainboardApi mainboard = null; public void makeComputer(AbstractFactory schema){ prepareHardwares(schema); } private void prepareHardwares(AbstractFactory schema){ //这里要去准备CPU和主板的具体实现,为了示例简单,这里只准备这两个 //但是,装机工程师并不知道如何去建立,怎么办呢? //使用抽象工厂来获取相应的接口对象 this.cpu = (CPUApi)schema.createProduct(1); this.mainboard = (MainboardApi)schema.createProduct(2); //测试一下配件是否好用 this.cpu.calculate(); this.mainboard.installCPU(); } }
经过上面的示例,能看到可扩展工厂的基本实现。从客户端的代码会发现,为何说这种方式是不太安全的呢?
你会发现建立产品对象返回来事后,须要造型成为具体的对象,由于返回的是Object,若是这个时候没有匹配上,好比返回的不是CPU对象,可是要强制造型成为CPU,那么就会发生错误,所以这种实现方式的一个潜在缺点就是不太安全。
假如如今要加入一个新的产品——内存,固然能够提供一个新的装机方案来使用它,这样已有的代码就不须要变化了。
先看看内存的接口吧,示例代码以下:
/** * 内存的接口 */ public interface MemoryApi { /** * 示意方法,内存具备缓存数据的能力 */ public void cacheData(); }
提供一个现代内存的基本实现,示例代码以下:
/** * 现代内存的类 */ public class HyMemory implements MemoryApi{ public void cacheData() { System.out.println("如今正在使用现代内存"); } }
如今想要使用这个新加入的产品,之前实现的代码都不用变化,只需新添加一个方案,在这个方案里面使用新的产品,而后客户端使用这个新的方案便可,示例代码以下:
/** * 装机方案三:Intel 的CPU + 技嘉的主板 + 现代的内存 */ public class Scheme3 implements AbstractFactory{ public Object createProduct(int type) { Object retObj = null; //type为1表示建立CPU,type为2表示建立主板,type为3表示建立内存 if(type==1){ retObj = new IntelCPU(1156); }else if(type==2){ retObj = new GAMainboard(1156); } //建立新添加的产品 else if(type==3){ retObj = new HyMemory(); } return retObj; } }
这个时候的装机工程师类,若是要建立带内存的机器,须要在装机工程师类里面添加对内存的使用,示例代码以下:
public class ComputerEngineer { private CPUApi cpu= null; private MainboardApi mainboard = null; /** * 定义组装机器须要的内存 */ private MemoryApi memory = null; public void makeComputer(AbstractFactory schema){ prepareHardwares(schema); } private void prepareHardwares(AbstractFactory schema){ //使用抽象工厂来获取相应的接口对象 this.cpu = (CPUApi)schema.createProduct(1); this.mainboard = (MainboardApi)schema.createProduct(2); this.memory = (MemoryApi)schema.createProduct(3); //测试一下配件是否好用 this.cpu.calculate(); this.mainboard.installCPU(); if(memory!=null){ this.memory.cacheData(); } } }
##3.3 抽象工厂模式和DAO##
DAO:数据访问对象,是Data Access Object首字母的简写。
DAO是JEE(也称JavaEE,原J2EE)中的一个标准模式,经过它来解决访问数据对象所面临的一系列问题,好比:数据源不一样、存储类型不一样、访问方式不一样、供应商不一样、版本不一样等等,这些不一样会形成访问数据的实现上差异很大。
数据源的不一样,好比存放于数据库的数据源,存放于LDAP(轻型目录访问协议)的数据源;又好比存放于本地的数据源和远程服务器上的数据源等等
存储类型的不一样,好比关系型数据库(RDBMS)、面向对象数据库(ODBMS)、纯文件、XML等等
访问方式的不一样,好比访问关系型数据库,能够用JDBC、EntityBean、JPA等来实现,固然也能够采用一些流行的框架,如Hibernate、IBatis等等
供应商的不一样,好比关系型数据库,流行如Oracel、DB二、SqlServer、MySql等等,它们的供应商是不一样的
版本不一样,好比关系型数据库,不一样的版本,实现的功能是有差别的,就算是对标准的SQL的支持,也是有差别的
可是对于须要进行数据访问的逻辑层而言,它可不想面对这么多不一样,也不想处理这么多差别,它但愿能以一个统一的方式来访问数据。此时系统结构如图所示:
也就是说,DAO须要抽象和封装全部对数据的访问,DAO承担和数据仓库交互的职责,这也意味着,访问数据所面临的全部问题,都须要DAO在内部来自行解决。
事实上,在实现DAO模式的时候,最多见的实现策略就是使用工厂的策略,并且可能是经过抽象工厂模式来实现,固然在使用抽象工厂模式来实现的时候,能够结合工厂方法模式
。所以DAO模式和抽象工厂模式有很大的联系。
(1)采用工厂方法模式
假如如今在一个订单处理的模块里面,你们都知道,订单一般又分红两个部分,一个部分是订单主记录或者是订单主表,另外一个部分是订单明细记录或者是订单子表,那么如今业务对象须要操做订单的主记录,也须要操做订单的子记录。
若是这个时候的业务比较简单,并且对数据的操做是固定的,好比就是操做数据库,无论订单的业务如何变化,底层数据存储都是同样的,那么这种状况下,能够采用工厂方法模式,此时系统结构如图所示:
从上面的结构示意图能够看出,若是底层存储固定的时候,DAOFactory就至关于工厂方法模式中的Creator,在里面定义两个工厂方法,分别建立订单主记录的DAO对象和建立订单子记录的DAO对象,由于固定是数据库实现,所以提供一个具体的工厂RdbDAOFactory(Rdb:关系型数据库),来实现对象的建立。也就是说DAO能够采用工厂方法模式来实现。
采用工厂方法模式的状况,要求DAO底层存储实现方式是固定的,这种多用在一些简单的小项目开发上。
(2)采用抽象工厂模式
实际上更多的时候,DAO底层存储实现方式是不固定的,DAO一般会支持多种存储实现方式,具体使用哪种存储方式多是由应用动态决定,或者是经过配置来指定。这种状况多见于产品开发、或者是稍复杂的应用、或者是较大的项目中。
对于底层存储方式不固定的时候,通常是采用抽象工厂模式来实现DAO。好比如今的实现除了RDB的实现,还会有Xml的实现,它们会被应用动态的选择,此时系统结构如图所示:
从上面的结构示意图能够看出,采用抽象工厂模式来实现DAO的时候,DAOFactory就至关于抽象工厂,里面定义一系列建立相关对象的方法,分别是建立订单主记录的DAO对象和建立订单子记录的DAO对象,此时OrderMainDAO和OrderDetailDAO就至关于被建立的产品,RdbDAOFactory和XmlDAOFactory就至关于抽象工厂的具体实现,在它们里面会选择相应的具体的产品实现来建立对象。
(1)先看看抽象工厂的代码实现,示例代码以下:
/** * 抽象工厂,建立订单主、子记录对应的DAO对象 */ public abstract class DAOFactory { /** * 建立订单主记录对应的DAO对象 * @return 订单主记录对应的DAO对象 */ public abstract OrderMainDAO createOrderMainDAO(); /** * 建立订单子记录对应的DAO对象 * @return 订单子记录对应的DAO对象 */ public abstract OrderDetailDAO createOrderDetailDAO(); }
(2)看看产品对象的接口,就是订单主、子记录的DAO定义,先看订单主记录的DAO定义,示例代码以下:
/** * 订单主记录对应的DAO操做接口 */ public interface OrderMainDAO { /** * 示意方法,保存订单主记录 */ public void saveOrderMain(); }
再看看订单子记录的DAO定义,示例代码以下:
/** * 订单子记录对应的DAO操做接口 */ public interface OrderDetailDAO { /** * 示意方法,保存订单子记录 */ public void saveOrderDetail(); }
(3)接下来实现订单主、子记录的DAO,先看关系型数据库的实现方式,示例代码以下:
public class RdbMainDAOImpl implements OrderMainDAO{ public void saveOrderMain() { System.out.println("now in RdbMainDAOImpl saveOrderMain"); } } public class RdbDetailDAOImpl implements OrderDetailDAO{ public void saveOrderDetail() { System.out.println("now in RdbDetailDAOImpl saveOrderDetail"); } }
Xml实现的方式同样,为了演示简单,都是输出了一句话,示例代码以下:
public class XmlMainDAOImpl implements OrderMainDAO{ public void saveOrderMain() { System.out.println("now in XmlMainDAOImpl saveOrderMain"); } }
(4)再看看具体的工厂实现,先看关系型数据库实现方式的工厂,示例代码以下:
public class RdbDAOFactory extends DAOFactory{ public OrderDetailDAO createOrderDetailDAO() { return new RdbDetailDAOImpl(); } public OrderMainDAO createOrderMainDAO() { return new RdbMainDAOImpl(); } }
Xml实现方式的工厂,示例代码以下:
public class XmlDAOFactory extends DAOFactory { public OrderDetailDAO createOrderDetailDAO() { return new XmlDetailDAOImpl(); } public OrderMainDAO createOrderMainDAO() { return new XmlMainDAOImpl(); } }
(5)好了,使用抽象工厂来简单的实现了DAO模式,那么在客户端,一般是由业务对象来调用DAO,那么该怎么使用这个DAO呢?示例代码以下:
public class BusinessObject { public static void main(String[] args) { //建立DAO的抽象工厂 DAOFactory df = new RdbDAOFactory(); //经过抽象工厂来获取须要的DAO接口 OrderMainDAO mainDAO = df.createOrderMainDAO(); OrderDetailDAO detailDAO = df.createOrderDetailDAO(); //调用DAO来完成数据存储的功能 mainDAO.saveOrderMain(); detailDAO.saveOrderDetail(); } }
经过上面的示例,能够看出DAO能够采用抽象工厂模式来实现,这也是大部分DAO实现采用的方式。
##3.4 模式的优缺点##
客户端使用抽象工厂来建立须要的对象,而客户端根本就不知道具体的实现是谁,客户端只是面向产品的接口编程而已,也就是说,客户端从具体的产品实现中解耦。
由于一个具体的工厂实现表明的是一个产品簇,好比上面例子的Scheme1表明装机方案一:Intel 的CPU + 技嘉的主板,若是要切换成为Scheme2,那就变成了装机方案二:AMD的CPU + 微星的主板。
客户端选用不一样的工厂实现,就至关因而在切换不一样的产品簇。
前面也提到这个问题了,若是须要给整个产品簇添加一个新的产品,那么就须要修改抽象工厂,这样就会致使修改全部的工厂实现类。在前面提供了一个能够扩展工厂的方式来解决这个问题,可是又不够安全。如何选择,根据实际应用来权衡吧。
在使用抽象工厂模式的时候,若是须要选择的层次过多,那么会形成整个类层次变得复杂。
举个例子来讲,就好比前面讲到的那个DAO的示例,如今这个DAO只有一个选择的层次,也就是选择一下是使用关系型数据库来实现,仍是用Xml来实现。如今考虑这样一种状况,若是关系型数据库实现里面又分红几种,好比:基于Oracle的实现,基于SqlServer的实现,基于MySql的实现等等。
那么客户端怎么选呢?不会把全部可能的实现状况所有都作到一个层次上吧,这个时候客户端就须要一层一层选择,也就是整个抽象工厂的实现也须要分出层次来,每一层负责一种选择,也就是一层屏蔽一种变化,这样很容易形成复杂的类层次结构。
##3.5 思考抽象工厂模式##
抽象工厂模式的本质:选择产品簇的实现。
工厂方法是选择单个产品的实现,虽然一个类里面能够有多个工厂方法,可是这些方法之间通常是没有联系的,即便看起来像有联系
。
可是抽象工厂着重的就是为一个产品簇选择实现,定义在抽象工厂里面的方法一般是有联系的,它们都是产品的某一部分或者是相互依赖的
。若是抽象工厂里面只定义一个方法,直接建立产品,那么就退化成为工厂方法了。
建议在以下状况中,选用抽象工厂模式:
若是但愿一个系统独立于它的产品的建立,组合和表示的时候,换句话说,但愿一个系统只是知道产品的接口,而不关心实现的时候。
若是一个系统要由多个产品系列中的一个来配置的时候,换句话说,就是能够动态的切换产品簇的时候。
若是要强调一系列相关产品的接口,以便联合使用它们的时候。
##3.6 相关模式##
这两个模式既有区别,又有联系,能够组合使用。
工厂方法模式通常是针对单独的产品对象的建立,而抽象工厂模式注重产品簇对象的建立,这是它们的区别。
若是把抽象工厂建立的产品簇简化,这个产品簇就只有一个产品,那么这个时候的抽象工厂跟工厂方法是差很少的,也就是**抽象工厂能够退化成工厂方法,而工厂方法又能够退化成简单工厂,这是它们的联系
**。
在抽象工厂的实现中,还可使用工厂方法来提供抽象工厂的具体实现,也就是说它们能够组合使用。
这两个模式能够组合使用。
在抽象工厂模式里面,具体的工厂实现,在整个应用中,一般一个产品系列只须要一个实例就能够了,所以能够把具体的工厂实现成为单例。