工厂方法模式

自作多情 提交于 2019-12-15 04:24:48

在现实生活中社会分工越来越细,越来越专业化。各种产品有专门的工厂生产,彻底告别了自给自足的小农经济时代,这大大缩短了产品的生产周期,提高了生产效率。同样,在软件开发中能否做到软件对象的生产和使用相分离呢?能否在满足“开闭原则”的前提下,客户随意增删或改变对软件相关对象的使用呢?这就是本节要讨论的问题。

模式的定义与特点

工厂方法(FactoryMethod)模式的定义:定义一个创建产品对象的工厂接口,将产品对象的实际创建工作推迟到具体子工厂类当中。这满足创建型模式中所要求的“创建与使用相分离”的特点。

我们把被创建的对象称为“产品”,把创建产品的对象称为“工厂”。如果要创建的产品不多,只要一个工厂类就可以完成,这种模式叫“简单工厂模式”,它不属于 GoF 的 23 种经典设计模式,它的缺点是增加新产品时会违背“开闭原则”。

本节介绍的“工厂方法模式”是对简单工厂模式的进一步抽象化,其好处是可以使系统在不修改原来代码的情况下引进新的产品,即满足开闭原则。

工厂方法模式的主要优点有:

  • 用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程;
  • 在系统增加新的产品时只需要添加具体产品类和对应的具体工厂类,无须对原工厂进行任何修改,满足开闭原则;


其缺点是:每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,这增加了系统的复杂度。

模式的结构与实现

工厂方法模式由抽象工厂、具体工厂、抽象产品和具体产品等4个要素构成。本节来分析其基本结构和实现方法。

1. 模式的结构

工厂方法模式的主要角色如下。

  1. 抽象工厂(Abstract Factory):提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法 newProduct() 来创建产品。
  2. 具体工厂(ConcreteFactory):主要是实现抽象工厂中的抽象方法,完成具体产品的创建。
  3. 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能。
  4. 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。


其结构图如图 1 所示。
 

工厂方法模式的结构图
图1 工厂方法模式的结构图

2. 模式的实现

根据图 1 写出该模式的代码如下:


 
  1. package FactoryMethod;
  2. public class AbstractFactoryTest
  3. {
  4. public static void main(String[] args)
  5. {
  6. try
  7. {
  8. Product a;
  9. AbstractFactory af;
  10. af=(AbstractFactory) ReadXML1.getObject();
  11. a=af.newProduct();
  12. a.show();
  13. }
  14. catch(Exception e)
  15. {
  16. System.out.println(e.getMessage());
  17. }
  18. }
  19. }
  20. //抽象产品:提供了产品的接口
  21. interface Product
  22. {
  23. public void show();
  24. }
  25. //具体产品1:实现抽象产品中的抽象方法
  26. class ConcreteProduct1 implements Product
  27. {
  28. public void show()
  29. {
  30. System.out.println("具体产品1显示...");
  31. }
  32. }
  33. //具体产品2:实现抽象产品中的抽象方法
  34. class ConcreteProduct2 implements Product
  35. {
  36. public void show()
  37. {
  38. System.out.println("具体产品2显示...");
  39. }
  40. }
  41. //抽象工厂:提供了厂品的生成方法
  42. interface AbstractFactory
  43. {
  44. public Product newProduct();
  45. }
  46. //具体工厂1:实现了厂品的生成方法
  47. class ConcreteFactory1 implements AbstractFactory
  48. {
  49. public Product newProduct()
  50. {
  51. System.out.println("具体工厂1生成-->具体产品1...");
  52. return new ConcreteProduct1();
  53. }
  54. }
  55. //具体工厂2:实现了厂品的生成方法
  56. class ConcreteFactory2 implements AbstractFactory
  57. {
  58. public Product newProduct()
  59. {
  60. System.out.println("具体工厂2生成-->具体产品2...");
  61. return new ConcreteProduct2();
  62. }
  63. }

 
  1. package FactoryMethod;
  2. import javax.xml.parsers.*;
  3. import org.w3c.dom.*;
  4. import java.io.*;
  5. class ReadXML1
  6. {
  7. //该方法用于从XML配置文件中提取具体类类名,并返回一个实例对象
  8. public static Object getObject()
  9. {
  10. try
  11. {
  12. //创建文档对象
  13. DocumentBuilderFactory dFactory=DocumentBuilderFactory.newInstance();
  14. DocumentBuilder builder=dFactory.newDocumentBuilder();
  15. Document doc;
  16. doc=builder.parse(new File("src/FactoryMethod/config1.xml"));
  17. //获取包含类名的文本节点
  18. NodeList nl=doc.getElementsByTagName("className");
  19. Node classNode=nl.item(0).getFirstChild();
  20. String cName="FactoryMethod."+classNode.getNodeValue();
  21. //System.out.println("新类名:"+cName);
  22. //通过类名生成实例对象并将其返回
  23. Class<?> c=Class.forName(cName);
  24. Object obj=c.newInstance();
  25. return obj;
  26. }
  27. catch(Exception e)
  28. {
  29. e.printStackTrace();
  30. return null;
  31. }
  32. }
  33. }


注意:该程序中用到了 XML 文件,如果想要获取该文件,请点击“下载”,就可以对其进行下载。

程序运行结果如下:

具体工厂1生成-->具体产品1...
具体产品1显示...


如果将 XML 配置文件中的 ConcreteFactory1 改为 ConcreteFactory2,则程序运行结果如下:

具体工厂2生成-->具体产品2...
具体产品2显示...

模式的应用实例

【例1】用工厂方法模式设计畜牧场。

分析:有很多种类的畜牧场,如养马场用于养马,养牛场用于养牛,所以该实例用工厂方法模式比较适合。

对养马场和养牛场等具体工厂类,只要定义一个生成动物的方法 newAnimal() 即可。由于要显示马类和牛类等具体产品类的图像,所以它们的构造函数中用到了 JPanel、JLabd 和 ImageIcon 等组件,并定义一个 show() 方法来显示它们。

客户端程序通过对象生成器类 ReadXML2 读取 XML 配置文件中的数据来决定养马还是养牛。其结构图如图 2 所示。
 

畜牧场结构图
图2 畜牧场结构图


注意:该程序中用到了 XML 文件,并且要显示马类和牛类等具体产品类的图像,如果想要获取 HTML 文件和图片,请点击“下载”,就可以对其进行下载。

程序代码如下:


 
  1. package FactoryMethod;
  2. import java.awt.*;
  3. import javax.swing.*;
  4. public class AnimalFarmTest
  5. {
  6. public static void main(String[] args)
  7. {
  8. try
  9. {
  10. Animal a;
  11. AnimalFarm af;
  12. af=(AnimalFarm) ReadXML2.getObject();
  13. a=af.newAnimal();
  14. a.show();
  15. }
  16. catch(Exception e)
  17. {
  18. System.out.println(e.getMessage());
  19. }
  20. }
  21. }
  22. //抽象产品:动物类
  23. interface Animal
  24. {
  25. public void show();
  26. }
  27. //具体产品:马类
  28. class Horse implements Animal
  29. {
  30. JScrollPane sp;
  31. JFrame jf=new JFrame("工厂方法模式测试");
  32. public Horse()
  33. {
  34. Container contentPane=jf.getContentPane();
  35. JPanel p1=new JPanel();
  36. p1.setLayout(new GridLayout(1,1));
  37. p1.setBorder(BorderFactory.createTitledBorder("动物:马"));
  38. sp=new JScrollPane(p1);
  39. contentPane.add(sp, BorderLayout.CENTER);
  40. JLabel l1=new JLabel(new ImageIcon("src/A_Horse.jpg"));
  41. p1.add(l1);
  42. jf.pack();
  43. jf.setVisible(false);
  44. jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //用户点击窗口关闭
  45. }
  46. public void show()
  47. {
  48. jf.setVisible(true);
  49. }
  50. }
  51. //具体产品:牛类
  52. class Cattle implements Animal
  53. {
  54. JScrollPane sp;
  55. JFrame jf=new JFrame("工厂方法模式测试");
  56. public Cattle()
  57. {
  58. Container contentPane=jf.getContentPane();
  59. JPanel p1=new JPanel();
  60. p1.setLayout(new GridLayout(1,1));
  61. p1.setBorder(BorderFactory.createTitledBorder("动物:牛"));
  62. sp=new JScrollPane(p1);
  63. contentPane.add(sp,BorderLayout.CENTER);
  64. JLabel l1=new JLabel(new ImageIcon("src/A_Cattle.jpg"));
  65. p1.add(l1);
  66. jf.pack();
  67. jf.setVisible(false);
  68. jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //用户点击窗口关闭
  69. }
  70. public void show()
  71. {
  72. jf.setVisible(true);
  73. }
  74. }
  75. //抽象工厂:畜牧场
  76. interface AnimalFarm
  77. {
  78. public Animal newAnimal();
  79. }
  80. //具体工厂:养马场
  81. class HorseFarm implements AnimalFarm
  82. {
  83. public Animal newAnimal()
  84. {
  85. System.out.println("新马出生!");
  86. return new Horse();
  87. }
  88. }
  89. //具体工厂:养牛场
  90. class CattleFarm implements AnimalFarm
  91. {
  92. public Animal newAnimal()
  93. {
  94. System.out.println("新牛出生!");
  95. return new Cattle();
  96. }
  97. }

 
  1. package FactoryMethod;
  2. import javax.xml.parsers.*;
  3. import org.w3c.dom.*;
  4. import java.io.*;
  5. class ReadXML2
  6. {
  7. public static Object getObject()
  8. {
  9. try
  10. {
  11. DocumentBuilderFactory dFactory=DocumentBuilderFactory.newInstance();
  12. DocumentBuilder builder=dFactory.newDocumentBuilder();
  13. Document doc;
  14. doc=builder.parse(new File("src/FactoryMethod/config2.xml"));
  15. NodeList nl=doc.getElementsByTagName("className");
  16. Node classNode=nl.item(0).getFirstChild();
  17. String cName="FactoryMethod."+classNode.getNodeValue();
  18. System.out.println("新类名:"+cName);
  19. Class<?> c=Class.forName(cName);
  20. Object obj=c.newInstance();
  21. return obj;
  22. }
  23. catch(Exception e)
  24. {
  25. e.printStackTrace();
  26. return null;
  27. }
  28. }
  29. }


程序的运行结果如图 3 所示。
 

畜牧场养殖的运行结果
图3 畜牧场养殖的运行结果

模式的应用场景

工厂方法模式通常适用于以下场景。

  • 客户只知道创建产品的工厂名,而不知道具体的产品名。如 TCL 电视工厂、海信电视工厂等。
  • 创建对象的任务由多个具体子工厂中的某一个完成,而抽象工厂只提供创建产品的接口。
  • 客户不关心创建产品的细节,只关心产品的品牌。

模式的扩展

当需要生成的产品不多且不会增加,一个具体工厂类就可以完成任务时,可删除抽象工厂类。这时工厂方法模式将退化到简单工厂模式,其结构图如图 4 所示。
 

简单工厂模式的结构图
图4 简单工厂模式的结构图

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!