《Java设计模式》之抽象工厂模式

场景问题

  举个生活中常见的样例——组装电脑。我们在组装电脑的时候。通常须要选择一系列的配件,比方CPU、硬盘、内存、主板、电源、机箱等。

为讨论使用简单点。仅仅考虑选择CPU和主板的问题。

  其实,在选择CPU的时候,面临一系列的问题。比方品牌、型号、针脚数目、主频等问题,仅仅有把这些问题都确定下来,才干确定详细的CPU。

  同样。在选择主板的时候,也有一系列问题。比方品牌、芯片组、集成芯片、总线频率等问题,也仅仅有这些都确定了。才干确定详细的主板。

  选择不同的CPU和主板,是每个客户在组装电脑的时候。向装机公司提出的要求。也就是我们每个人自己拟定的装机方案。

  在终于确定这个装机方案之前。还须要总体考虑各个配件之间的兼容性。比方:CPU和主板,假设使用Intel的CPU和AMD的主板是根本无法组装的。因为Intel的CPU针脚数与AMD主板提供的CPU插口不兼容,就是说假设使用Intel的CPU根本就插不到AMD的主板中,所以装机方案是总体性的,里面选择的各个配件之间是有关联的。

  对于装机project师而言。他仅仅知道组装一台电脑,须要对应的配件。但是详细使用什么样的配件,还得由客户说了算。也就是说装机project师仅仅是负责组装。而客户负责选择装配所须要的详细的配件。因此。当装机project师为不同的客户组装电脑时,仅仅须要依据客户的装机方案,去获取对应的配件,然后组装就可以。

使用简单工厂模式的解决方式

  考虑客户的功能。须要选择自己须要的CPU和主板,然后告诉装机project师自己的选择,接下来就等着装机project师组装电脑了。

  对装机project师而言,仅仅是知道CPU和主板的接口,而不知道详细实现,非常明显能够用上简单工厂模式或工厂方法模式。为了简单,这里选用简单工厂。

客户告诉装机project师自己的选择,然后装机project师会通过对应的工厂去获取对应的实例对象。

源码

CPU接口与详细实现

 

  1. package com.bankht.abstractFactory;  
  2. /**  
  3.  * @author: 特种兵—AK47  
  4.  * @创建时间:2012-6-19 下午04:39:35  
  5.  * 
  6.  * @类说明 :Cpu接口类 
  7.  */  
  8. public interface Cpu {  
  9.     public void calculate();  
  10. }  

  1. package com.bankht.abstractFactory;  
  2.   
  3. /** 
  4.  * @author: 特种兵—AK47 
  5.  * @创建时间:2012-6-19 下午04:45:34 
  6.  *  
  7.  * @类说明 :IntelCpu 
  8.  */  
  9. public class IntelCpu implements Cpu {  
  10.     /** 
  11.      * CPU的针脚数 
  12.      */  
  13.     private int pins = 0;  
  14.   
  15.     public IntelCpu(int pins) {  
  16.         this.pins = pins;  
  17.     }  
  18.   
  19.     @Override  
  20.     public void calculate() {  
  21.         System.out.println("Intel CPU的针脚数:" + pins);  
  22.     }  
  23.   
  24. }  

  1. package com.bankht.abstractFactory;  
  2.   
  3. /** 
  4.  * @author: 特种兵—AK47 
  5.  * @创建时间:2012-6-19 下午04:53:12 
  6.  *  
  7.  * @类说明 :AmdCpu 
  8.  */  
  9. public class AmdCpu implements Cpu {  
  10.     /** 
  11.      * CPU的针脚数 
  12.      */  
  13.     private int pins = 0;  
  14.   
  15.     public AmdCpu(int pins) {  
  16.         this.pins = pins;  
  17.     }  
  18.   
  19.     @Override  
  20.     public void calculate() {  
  21.         // TODO Auto-generated method stub  
  22.         System.out.println("AMD CPU的针脚数:" + pins);  
  23.     }  
  24. }  


主板接口与详细实现

  1. package com.bankht.abstractFactory;  
  2.   
  3. /** 
  4.  * @author: 特种兵—AK47 
  5.  * @创建时间:2012-6-19 下午04:53:57 
  6.  *  
  7.  * @类说明 :主板接口 
  8.  */  
  9. public interface Mainboard {  
  10.     public void installCPU();  
  11. }  


 

  1. package com.bankht.abstractFactory;  
  2.   
  3. /** 
  4.  * @author: 特种兵—AK47 
  5.  * @创建时间:2012-6-19 下午04:56:02 
  6.  *  
  7.  * @类说明 :Intel主板类 
  8.  */  
  9. public class IntelMainboard implements Mainboard {  
  10.     /** 
  11.      * CPU插槽的孔数 
  12.      */  
  13.     private int cpuHoles = 0;  
  14.   
  15.     /** 
  16.      * 构造方法,传入CPU插槽的孔数 
  17.      *  
  18.      * @param cpuHoles 
  19.      */  
  20.     public IntelMainboard(int cpuHoles) {  
  21.         this.cpuHoles = cpuHoles;  
  22.     }  
  23.   
  24.     @Override  
  25.     public void installCPU() {  
  26.         // TODO Auto-generated method stub  
  27.         System.out.println("Intel主板的CPU插槽孔数是:" + cpuHoles);  
  28.     }  
  29.   
  30. }  


 

  1. package com.bankht.abstractFactory;  
  2.   
  3. /** 
  4.  * @author: 特种兵—AK47 
  5.  * @创建时间:2012-6-19 下午04:56:51 
  6.  *  
  7.  * @类说明 :Amd主板类 
  8.  */  
  9. public class AmdMainboard implements Mainboard {  
  10.     /** 
  11.      * CPU插槽的孔数 
  12.      */  
  13.     private int cpuHoles = 0;  
  14.   
  15.     /** 
  16.      * 构造方法,传入CPU插槽的孔数 
  17.      *  
  18.      * @param cpuHoles 
  19.      */  
  20.     public AmdMainboard(int cpuHoles) {  
  21.         this.cpuHoles = cpuHoles;  
  22.     }  
  23.   
  24.     @Override  
  25.     public void installCPU() {  
  26.         // TODO Auto-generated method stub  
  27.         System.out.println("AMD主板的CPU插槽孔数是:" + cpuHoles);  
  28.     }  
  29. }  


CPU与主板工厂类

  1. package com.bankht.abstractFactory;  
  2.   
  3. /** 
  4.  * @author: 特种兵—AK47 
  5.  * @创建时间:2012-6-19 下午04:57:56 
  6.  *  
  7.  * @类说明 :Cpu工厂类 
  8.  */  
  9. public class CpuFactory {  
  10.     public static Cpu createCpu(int type) {  
  11.         Cpu cpu = null;  
  12.         if (type == 1) {  
  13.             cpu = new IntelCpu(755);  
  14.         } else if (type == 2) {  
  15.             cpu = new AmdCpu(938);  
  16.         }  
  17.         return cpu;  
  18.     }  
  19. }  


 

  1. package com.bankht.abstractFactory;  
  2.   
  3. /** 
  4.  * @author: 特种兵—AK47 
  5.  * @创建时间:2012-6-19 下午04:58:27 
  6.  *  
  7.  * @类说明 :主板工厂类 
  8.  */  
  9. public class MainboardFactory {  
  10.     public static Mainboard createMainboard(int type) {  
  11.         Mainboard mainboard = null;  
  12.         if (type == 1) {  
  13.             mainboard = new IntelMainboard(755);  
  14.         } else if (type == 2) {  
  15.             mainboard = new AmdMainboard(938);  
  16.         }  
  17.         return mainboard;  
  18.     }  
  19. }  


装机project师类与客户类执行结果例如以下:

  1. package com.bankht.abstractFactory;  
  2.   
  3. /** 
  4.  * @author: 特种兵—AK47 
  5.  * @创建时间:2012-6-19 下午04:59:14 
  6.  *  
  7.  * @类说明 :装机project师 
  8.  */  
  9. public class ComputerEngineer {  
  10.     /** 
  11.      * 定义组装机须要的CPU 
  12.      */  
  13.     private Cpu cpu = null;  
  14.     /** 
  15.      * 定义组装机须要的主板 
  16.      */  
  17.     private Mainboard mainboard = null;  
  18.   
  19.     public void makeComputer(int cpuType, int mainboard) {  
  20.         /** 
  21.          * 组装机器的基本步骤 
  22.          */  
  23.         // 1:首先准备好装机所须要的配件  
  24.         prepareHardwares(cpuType, mainboard);  
  25.         // 2:组装机器  
  26.         // 3:測试机器  
  27.         // 4:交付客户  
  28.     }  
  29.   
  30.     private void prepareHardwares(int cpuType, int mainboard) {  
  31.         // 这里要去准备CPU和主板的详细实现。为了演示样例简单。这里仅仅准备这两个  
  32.         // 但是。装机project师并不知道怎样去创建,怎么办呢?  
  33.   
  34.         // 直接找对应的工厂获取  
  35.         this.cpu = CpuFactory.createCpu(cpuType);  
  36.         this.mainboard = MainboardFactory.createMainboard(mainboard);  
  37.   
  38.         // 測试配件是否好用  
  39.         this.cpu.calculate();  
  40.         this.mainboard.installCPU();  
  41.     }  
  42. }  


 

  1. package com.bankht.abstractFactory;  
  2.   
  3. import org.junit.Test;  
  4.   
  5. /** 
  6.  * @author: 特种兵—AK47 
  7.  * @创建时间:2012-6-19 下午05:01:04 
  8.  *  
  9.  * @类说明 :客户測试类 
  10.  */  
  11. public class Client {  
  12.   
  13.     @Test  
  14.     public void test() {  
  15.         ComputerEngineer cf = new ComputerEngineer();  
  16.         cf.makeComputer(11);  
  17.     }  
  18. }  


执行结果例如以下:

  上面的实现,尽管通过简单工厂方法攻克了:对于装机project师。仅仅知CPU和主板的接口,而不知道详细实现的问题。但另一个问题没有解决。那就是这些CPU对象和主板对象其实是有关系的。须要相互匹配的。而上面的实现中,并没有维护这种关联关系,CPU和主板是由客户随意选择,这是有问题的。比方在client调用makeComputer时,传入參数为(1,2),执行结果例如以下:

观察上面结果就会看出问题。客户选择的是Intel的CPU针脚数为755,而选择的主板是AMD。主板上的CPU插孔是938。根本无法组装,这就是没有维护配件之间的关系造成的。该怎么解决问题呢?  

引进抽象工厂模式

  每个模式都是针对一定问题的解决方式。

抽象工厂模式与工厂方法模式的最大差别就在于。工厂方法模式针对的是一个产品等级结构;而抽象工厂模式则须要面对多个产品等级结构。

  在学习抽象工厂详细实例之前,应该明确两个重要的概念:产品族和产品等级。

  所谓产品族,是指位于不同产品等级结构中,功能相关联的产品组成的家族。比方AMD的主板、芯片组、CPU组成一个家族,Intel的主板、芯片组、CPU组成一个家族。

而这两个家族都来自于三个产品等级:主板、芯片组、CPU。一个等级结构是由同样的结构的产品组成,示意图例如以下:

  显然,每个产品族中含有产品的数目,与产品等级结构的数目是相等的。产品的等级结构与产品族将产品依照不同方向划分。形成一个二维的坐标系。

横轴表示产品的等级结构,纵轴表示产品族,上图共同拥有两个产品族,分布于三个不同的产品等级结构中。

仅仅要指明一个产品所处的产品族以及它所属的等级结构,就能够唯一的确定这个产品。

  上面所给出的三个不同的等级结构具有平行的结构。因此,假设採用工厂方法模式。就势必要使用三个独立的工厂等级结构来对付这三个产品等级结构。因为这三个产品等级结构的类似性,会导致三个平行的工厂等级结构。随着产品等级结构的数目的添加,工厂方法模式所给出的工厂等级结构的数目也会随之添加。例如以下图:

    那么,能否够使用同一个工厂等级结构来对付这些同样或者极为类似的产品等级结构呢?当然能够的,并且这就是抽象工厂模式的优点。同一个工厂等级结构负责三个不同产品等级结构中的产品对象的创建。

 

  能够看出,一个工厂等级结构能够创建出分属于不同产品等级结构的一个产品族中的全部对象。

显然,这时候抽象工厂模式比简单工厂模式、工厂方法模式更有效率。

对应于每个产品族都有一个详细工厂。而每个详细工厂负责创建属于同一个产品族。但是分属于不同等级结构的产品。

抽象工厂模式结构

  抽象工厂模式是对象的创建模式,它是工厂方法模式的进一步推广。

  假设一个子系统须要一些产品对象,而这些产品又属于一个以上的产品等级结构。那么为了将消费这些产品对象的责任和创建这些产品对象的责任切割开来。能够引进抽象工厂模式。这种话。消费产品的一方不须要直接參与产品的创建工作。而仅仅须要向一个公用的工厂接口请求所须要的产品。

  通过使用抽象工厂模式,能够处理具有同样(或者类似)等级结构中的多个产品族中的产品对象的创建问题。例如以下图所看到的:

  因为这两个产品族的等级结构同样,因此使用同一个工厂族也能够处理这两个产品族的创建问题,这就是抽象工厂模式。

  依据产品角色的结构图,就不难给出工厂角色的结构设计图。

  能够看出。每个工厂角色都有两个工厂方法,分别负责创建分属不同产品等级结构的产品对象。

源码

  前面演示样例实现的CPU接口和CPU实现对象,主板接口和主板实现对象,都不须要变化。

  前面演示样例中创建CPU的简单工厂和创建主板的简单工厂。都不再须要。

  新加入的抽象工厂类和实现类:

  1. package com.bankht.abstractFactory;  
  2.   
  3. /** 
  4.  * @author: 特种兵—AK47 
  5.  * @创建时间:2012-6-19 下午05:50:28 
  6.  *  
  7.  * @类说明 : 
  8.  */  
  9. public interface AbstractFactory {  
  10.     /** 
  11.      * 创建CPU对象 
  12.      *  
  13.      * @return CPU对象 
  14.      */  
  15.     public Cpu createCpu();  
  16.   
  17.     /** 
  18.      * 创建主板对象 
  19.      *  
  20.      * @return 主板对象 
  21.      */  
  22.     public Mainboard createMainboard();  
  23. }  


 

  1. package com.bankht.abstractFactory;  
  2.   
  3. /** 
  4.  * @author: 特种兵—AK47 
  5.  * @创建时间:2012-6-19 下午05:50:55 
  6.  *  
  7.  * @类说明 : 
  8.  */  
  9. public class IntelFactory implements AbstractFactory {  
  10.   
  11.     @Override  
  12.     public Cpu createCpu() {  
  13.         // TODO Auto-generated method stub  
  14.         return new IntelCpu(755);  
  15.     }  
  16.   
  17.     @Override  
  18.     public Mainboard createMainboard() {  
  19.         // TODO Auto-generated method stub  
  20.         return new IntelMainboard(755);  
  21.     }  
  22.   
  23. }  


 

  1. package com.bankht.abstractFactory;  
  2.   
  3. /** 
  4.  * @author: 特种兵—AK47 
  5.  * @创建时间:2012-6-19 下午05:51:18 
  6.  *  
  7.  * @类说明 : 
  8.  */  
  9. public class AmdFactory implements AbstractFactory {  
  10.   
  11.     @Override  
  12.     public Cpu createCpu() {  
  13.         // TODO Auto-generated method stub  
  14.         return new IntelCpu(938);  
  15.     }  
  16.   
  17.     @Override  
  18.     public Mainboard createMainboard() {  
  19.         // TODO Auto-generated method stub  
  20.         return new IntelMainboard(938);  
  21.     }  
  22.   
  23. }  


  装机project师类跟前面的实现相比,基本的变化是:从client不再传入选择CPU和主板的參数,而是直接传入客户已经选择好的产品对象。

这样就避免了单独去选择CPU和主板所带来的兼容性问题,客户要选就是一套。就是一个系列。

  1. package com.bankht.abstractFactory;  
  2.   
  3. /** 
  4.  * @author: 特种兵—AK47 
  5.  * @创建时间:2012-6-19 下午04:59:14 
  6.  *  
  7.  * @类说明 :装机project师 
  8.  */  
  9. public class ComputerEngineer {  
  10.     /** 
  11.      * 定义组装机须要的CPU 
  12.      */  
  13.     private Cpu cpu = null;  
  14.     /** 
  15.      * 定义组装机须要的主板 
  16.      */  
  17.     private Mainboard mainboard = null;  
  18.   
  19.     public void makeComputer(AbstractFactory af) {  
  20.         /** 
  21.          * 组装机器的基本步骤 
  22.          */  
  23.         // 1:首先准备好装机所须要的配件  
  24.         prepareHardwares(af);  
  25.         // 2:组装机器  
  26.         // 3:測试机器  
  27.         // 4:交付客户  
  28.     }  
  29.   
  30.     private void prepareHardwares(AbstractFactory af) {  
  31.         // 这里要去准备CPU和主板的详细实现,为了演示样例简单。这里仅仅准备这两个  
  32.         // 但是。装机project师并不知道怎样去创建。怎么办呢?  
  33.   
  34.         // 直接找对应的工厂获取  
  35.         this.cpu = af.createCpu();  
  36.         this.mainboard = af.createMainboard();  
  37.   
  38.         // 測试配件是否好用  
  39.         this.cpu.calculate();  
  40.         this.mainboard.installCPU();  
  41.     }  
  42. }  


client代码:

  1. package com.bankht.abstractFactory;  
  2.   
  3. import org.junit.Test;  
  4.   
  5. /** 
  6.  * @author: 特种兵—AK47 
  7.  * @创建时间:2012-6-19 下午05:01:04 
  8.  *  
  9.  * @类说明 :客户測试类 
  10.  */  
  11. public class Client {  
  12.   
  13.     @Test  
  14.     public void test() {  
  15.         // 创建装机project师对象  
  16.         ComputerEngineer cf = new ComputerEngineer();  
  17.         // 客户选择并创建须要使用的产品对象  
  18.         AbstractFactory af = new AmdFactory();  
  19.         // 告诉装机project师自己选择的产品,让装机project师组装电脑  
  20.         cf.makeComputer(af);  
  21.     }  
  22. }  


  抽象工厂的功能是为一系列相关对象或相互依赖的对象创建一个接口。一定要注意,这个接口内的方法不是随意堆砌的,而是一系列相关或相互依赖的方法。比方上面样例中的主板和CPU,都是为了组装一台电脑的相关对象。不同的装机方案,代表一种详细的电脑系列。

  
  因为抽象工厂定义的一系列对象一般是相关或相互依赖的,这些产品对象就构成了一个产品族。也就是抽象工厂定义了一个产品族。

  这就带来非常大的灵活性,切换产品族的时候,仅仅要提供不同的抽象工厂实现就能够了。也就是说如今是以一个产品族作为一个总体被切换


在什么情况下应当使用抽象工厂模式

  1.一个系统不应当依赖于产品类实例怎样被创建、组合和表达的细节,这对于全部形态的工厂模式都是重要的。

  2.这个系统的产品有多于一个的产品族,而系统仅仅消费当中某一族的产品。

  3.同属于同一个产品族的产品是在一起使用的,这一约束必须在系统的设计中体现出来。(比方:Intel主板必须使用Intel CPU、Intel芯片组)

  4.系统提供一个产品类的库。全部的产品以同样的接口出现。从而使client不依赖于实现。

抽象工厂模式的起源

  抽象工厂模式的起源或者最早的应用,是用于创建分属于不同操作系统的视窗构建。

比方:命令按键(Button)与文字框(Text)都是视窗构建,在UNIX操作系统的视窗环境和Windows操作系统的视窗环境中,这两个构建有不同的本地实现,它们的细节有所不同。

  在每个操作系统中。都有一个视窗构建组成的构建家族。在这里就是Button和Text组成的产品族。

而每个视窗构件都构成自己的等级结构,由一个抽象角色给出抽象的功能描写叙述。而由详细子类给出不同操作系统下的详细实现。

  能够发如今上面的产品类图中。有两个产品的等级结构。各自是Button等级结构和Text等级结构。同一时候有两个产品族。也就是UNIX产品族和 Windows产品族。UNIX产品族由UNIX Button和UNIX Text产品构成;而Windows产品族由Windows Button和Windows Text产品构成。

    系统对产品对象的创建需求由一个project的等级结构满足,当中有两个详细project角色,即UnixFactory和WindowsFactory。 UnixFactory对象负责创建Unix产品族中的产品,而WindowsFactory对象负责创建Windows产品族中的产品。这就是抽象工厂模式的应用,抽象工厂模式的解决方式例如以下图:

  显然,一个系统仅仅能够在某一个操作系统的视窗环境下执行,而不能同一时候在不同的操作系统上执行。所以,系统实际上仅仅能消费属于同一个产品族的产品。

  在现代的应用中,抽象工厂模式的使用范围已经大大扩大了,不再要求系统仅仅能消费某一个产品族了。

因此。能够不必理会前面所提到的原始用意。

抽象工厂模式的优点

  • 分离接口和实现

  client使用抽象工厂来创建须要的对象,而client根本就不知道详细的实现是谁。client仅仅是面向产品的接口编程而已。也就是说,client从详细的产品实现中解耦。

  • 使切换产品族变得easy

  因为一个详细的工厂实现代表的是一个产品族。比方上面样例的从Intel系列到AMD系列仅仅须要切换一下详细工厂。

抽象工厂模式的缺点

  • 不太easy扩展新的产品

  假设须要给整个产品族加入一个新的产品。那么就须要改动抽象工厂,这样就会导致改动全部的工厂实现类。

 


 

原文地址:https://www.cnblogs.com/yjbjingcha/p/7028535.html