建立对象与使用对象——谈谈工厂的做用

在设计模式的教学和推广过程当中,不少企业学员和在校学生常常问我,工厂模式(包括简单工厂模式、工厂方法模式和抽象工厂模式)到底有什么用,不少时候经过反射机制就能够很灵活地建立对象,为毛还要工厂?微笑,在本文中我将围绕建立对象和使用对象来简单谈谈工厂的做用。java

      与一个对象相关的职责一般有三类:对象自己所具备的职责、建立对象的职责和使用对象的职责。对象自己的职责比较容易理解,就是对象自身所具备的一些数据和行为,可经过一些公开的方法来实现它的职责。在本文中,咱们将简单讨论一下对象的建立职责和使用职责。设计模式

      在Java语言中,咱们一般有如下几种建立对象的方式:app

       (1) 使用new关键字直接建立对象;函数

       (2) 经过反射机制建立对象;oop

       (3) 经过clone()方法建立对象;this

       (4) 经过工厂类建立对象。spa

      毫无疑问,在客户端代码中直接使用new关键字是最简单的一种建立对象的方式,可是它的灵活性较差,下面经过一个简单的示例来加以说明: .net

[java] view plain copy
  1. class LoginAction {  
  2.     private UserDAO udao;  
  3.       
  4.     public LoginAction() {  
  5.         udao = new JDBCUserDAO(); //建立对象  
  6.     }  
  7.       
  8.     public void execute() {  
  9.         //其余代码  
  10.         udao.findUserById(); //使用对象  
  11.         //其余代码  
  12.     }  
  13. }  

      LoginAction类中定义了一个UserDAO类型的对象udao,在LoginAction的构造函数中建立了JDBCUserDAO类型的udao对象,并在execute()方法中调用了udao对象的findUserById()方法,这段代码看上去并无什么问题。下面咱们来分析一下LoginAction和UserDAO之间的关系,LoginAction类负责建立了一个UserDAO子类的对象并使用UserDAO的方法来完成相应的业务处理,也就是说LoginAction即负责udao的建立又负责udao的使用,建立对象和使用对象的职责耦合在一块儿,这样的设计会致使一个很严重的问题:若是在LoginAction中但愿可以使用UserDAO的另外一个子类如HibernateUserDAO类型的对象,必须修改LoginAction类的源代码,违反了“开闭原则”。如何解决该问题?设计

      最经常使用的一种解决方法是将udao对象的建立职责从LoginAction类中移除,在LoginAction类以外建立对象,那么谁来负责建立UserDAO对象呢?答案是:工厂类。经过引入工厂类,客户类(如LoginAction)不涉及对象的建立,对象的建立者也不会涉及对象的使用。引入工厂类UserDAOFactory以后的结构如图1所示:对象

1 引入工厂类以后的结构图

       工厂类的引入将下降由于产品或工厂类改变所形成的维护工做量。若是UserDAO的某个子类的构造函数发生改变或者要须要添加或移除不一样的子类,只要维护UserDAOFactory的代码,而不会影响到LoginAction;若是UserDAO的接口发生改变,例如添加、移除方法或改变方法名,只须要修改LoginAction,不会给UserDAOFactory带来任何影响。

       在全部的工厂模式中,咱们都强调一点:两个类A和B之间的关系应该仅仅是A建立B或是A使用B,而不能两种关系都有。将对象的建立和使用分离,也使得系统更加符合“单一职责原则”,有利于对功能的复用和系统的维护。

       此外,将对象的建立和使用分离还有一个好处:防止用来实例化一个类的数据和代码在多个类中处处都是,能够将有关建立的知识搬移到一个工厂类中,这在Joshua Kerievsky的《重构与模式》一书中有专门的一节来进行介绍。由于有时候咱们建立一个对象不仅是简单调用其构造函数,还须要设置一些参数,可能还须要配置环境,若是将这些代码散落在每个建立对象的客户类中,势必会出现代码重复、建立蔓延的问题,而这些客户类其实无须承担对象的建立工做,它们只需使用已建立好的对象就能够了。此时,能够引入工厂类来封装对象的建立逻辑和客户代码的实例化/配置选项。

      使用工厂类还有一个“不是特别明显的”优势,一个类可能拥有多个构造函数,而在Java、C#等语言中构造函数名字都与类名相同,客户端只能经过传入不一样的参数来调用不一样的构造函数建立对象,从构造函数和参数列表中也许你们根本不了解不一样构造函数所构造的产品的差别。但若是将对象的建立过程封装在工厂类中,咱们能够提供一系列名字彻底不一样的工厂方法,每个工厂方法对应一个构造函数,客户端能够以一种更加可读、易懂的方式来建立对象,并且,从一组工厂方法中选择一个意义明确的工厂方法,比从一组名称相同参数不一样的构造函数中选择一个构造函数要方便不少。如图2所示:

        在图2中,矩形工厂类RectangleFactory提供了两个工厂方法createRectangle()和createSquare(),一个用于建立长方形,一个用于建立正方形,这两个方法比直接经过构造函数来建立长方形或正方形对象意义更加明确,也在必定程度上下降了客户端调用时出错的几率。

      那么,有人可能会问,是否须要为设计中的每个类都配备一个工厂类?答案是:具体状况具体分析。若是产品类很简单,并且不存在太多变数,其构造过程也很简单,此时无须为其提供工厂类,直接在使用以前实例化便可,例如Java语言中的String类,咱们就无须为它专门提供一个StringFactory,这样作反而有点像杀鸡用牛刀,大材小用,并且会致使工厂泛滥,增长系统的复杂度。

相关文章
相关标签/搜索