9.3.2 简单工厂
依赖关系
对于一个典型的Java
应用而言,应用之中各实例之间存在复杂的调用关系(Spring
把这种调用关系称为依赖关系,例如A实例调用B实例的方法,则称为A依赖于B)。
硬编码耦合
当A对象需要调用B对象的方法时,许多初学者会选择使用new
关键字来创建一个B实例,然后调用B实例的方法。从语法的角度来看,这种做法没有任何问题,这种做法的坏处在于:A类的方法实现直接调用了B类的类名(这种方式也被称为硬编码耦合
),一旦系统需要重构:需要使用C类来代替B类时,程序不得不改写A类代码。如果应用中有100个或10000个类以硬编码方式耦合了B类,则需要重新改写100个、10000个地方……这显然是一种非常可怕的事情。
换一个角度来看这个问题:对于A对象而言,它只需要调用B对象的方法,并不是关心B对象的实现、创建过程。考虑让B类实现一个IB接口,而A类只需要IB接口耦合。具体做法是:
定义一个工厂类:IBFactory
,由IBFactory
工厂类来负责创建IB实例;而A类通过调用IBFactory
工厂的方法来得到IB的实例。这样A类就不用直接使用new
关键字来创建B实例。
通过改用上面设计,则A类不但需要与IBFactory
耦合,还需要与IB
接口耦合;如果系统需要重构:需要使用C类代替B类,则只需要让C类也实现IB接口,并改写IBFactory
工厂中创建IB实例的实现代码,让该工厂产生实现了IB接口的C实例即可。由于所有依赖IB实例的对象都是通过工厂来获取IB实例的,所以它们都将改为获得C实例,这就完成了系统重构。
这种将多个类对象交给工厂类来生成的设计方式被称为简单工厂模式。
程序示例
1 2 3 4 5 6 7
| E:\workspace_QingLiangJiJavaEEQiYeYingYongShiZhang5\SimpleFactory └─src\ ├─BetterPrinter.java ├─Computer.java ├─Output.java ├─OutputFactory.java └─Printer.java
|
下面以一个简单的场景来介绍简单工厂模式。假设程序中有个Computer
对象需要依赖一个输出设备,现在有两个选择:直接让Computer
对象依赖一个Printer
(实现类)对象,或者让Computer
依赖Output
(接口)属性。
在这种应用场景下,使用简单工厂模式可以让系统具有更好的可维护性、可扩展性。根据工厂模式,程序应该让Computer
依赖一个Output
属性,将Computer
类与Printer
实现类分离开来。 Computer
对象只需面向Output
接口编程即可。
下面是这个Computer
类定义的代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| public class Computer { private Output out;
public Computer(Output out) { this.out = out; } public void keyIn(String msg) { out.getData(msg); } public void print() { out.out(); } public static void main(String[] args) { OutputFactory of = new OutputFactory(); Computer c = new Computer(of.getOutput()); c.keyIn("轻量级Java EE企业应用实战"); c.keyIn("疯狂Java讲义"); c.print(); } }
|
从上面粗体字代码可以看出,该Computer
类已经完全与Output
实现类分离了,它只与该接口耦合。而且, Computer
不再负责创建Output
对象,系统将提供一个Output
工厂来负责生成Output
对象。这个OutputFactory
工厂类代码如下。
1 2 3 4 5 6 7
| public class OutputFactory { public Output getOutput() { return new Printer(); } }
|
在该OutputFactory
类中包含了一个getOutput()
方法,该方法返回一个Output
实现类的实例。该方法负责创建Output
实例,具体创建哪一个实现类的对象由该方法决定,
Printer
类的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| public class Printer implements Output { private String[] printData = new String[MAX_CACHE_LINE]; private int dataNum = 0; public void out() { while(dataNum > 0) { System.out.println("打印机打印:" + printData[0]); System.arraycopy(printData , 1, printData, 0, --dataNum); } } public void getData(String msg) { if (dataNum >= MAX_CACHE_LINE) { System.out.println("输出队列已满,添加失败"); } else { printData[dataNum++] = msg; } } }
|
上面的Printer
类模拟了一个简单的打印机,如果系统需要重构,需要使用BetterPrinter
来代替Printer
类,则只需要让BetterPrinter
实现Output
接口,并改写OutputFactory
类的getOutput()
方法即可。
下面是BetterPrinter
实现类的代码。 BetterPrinter
只是对原有的Printer
进行简单修改,这里用来模拟系统重构后的改进。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| public class BetterPrinter implements Output { private String[] printData = new String[MAX_CACHE_LINE * 2]; private int dataNum = 0; public void out() { while(dataNum > 0) { System.out.println("高速打印机正在打印:" + printData[0]); System.arraycopy(printData , 1, printData, 0, --dataNum); } } public void getData(String msg) { if (dataNum >= MAX_CACHE_LINE * 2) { System.out.println("输出队列已满,添加失败"); } else { printData[dataNum++] = msg; } } }
|
上面程序中的BetterPrinter
类与Printer
并无太大区别,仅仅略微改变了Printer
实现,且BetterPrinter
也实现了Output
接口,因此也可当成Output
对象使用,因此只要把OutputFactory
工厂类的getOutput()
方法中改为如下代即可:
1 2 3 4 5 6 7
| public class OutputFactory { public Output getOutput() { return new BetterPrinter(); } }
|
再次运行前面的Computer.java
程序,发现Computer
所依赖的Output
对象已改为Better Printer
对象,而不再是原来的Printer
对象。
通过这种方式,可以把所有生成Output
对象的逻辑集中在OutputFactory
工厂类中管理,而所有需要使用Output
对象的类只需与Output
接口耦合,而不是与具体的实现类耦合。即使系统中有很多类依赖了Printer
对象,只要OutputFactory
类的getOutput()
方法返回BetterPrinter
对象,则它们全部将会改为依赖BetterPrinter
对象,而其他程序无须修改,只需要修改OutputFactory
工厂的getOutput()
的方法实现即可。
简单工厂模式的优势
使用简单工厂模式的优势是:让对象的调用者和对象创建过程分离,当对象调用者需要对象时,直接向工厂请求即可;从而避免了对象的调用者与对象的实现类以硬编码方式耦合,以提高系统的可维护性、可扩展性。
工厂模式的缺陷
工厂模式也有一个小小的缺陷:当产品修改时,工厂类也要做相应的修改。
对Spring
容器而言,它首先是一个巨大的工厂,它负责创建所有Bean
实例,整个应用的所有组件都由Spring
容器负责创建。不仅如此, Spring
容器扩展了这种简单工厂模式,它还可以管理Bean
实例之间的依赖关系;而且,如果容器中Bean
实例具有singleton
行为特征,则Spring
容器还会缓存该Bean
实例,从而保证程序通过Spring
工厂来获取该Bean
实例时, Spring
工厂将会返回同一个Bean
实例。
实现简单的IoC容器功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| E:\workspace_QingLiangJiJavaEEQiYeYingYongShiZhang5\IoC ├─beans.xml └─src\ ├─lee\ │ ├─BetterPrinter.java │ ├─Computer.java │ ├─IoCTest.java │ ├─Output.java │ └─Printer.java └─org\ └─crazyit\ └─ioc\ ├─ApplicationContext.java └─CrazyitXmlApplicationContext.java
|
下面的示例提供一份类似于Spring
配置文件的XML
文件,程序提供一个扩展的工厂类,该工厂类也可提供类似于Spring loc
容器的功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <?xml version="1.0" encoding="GBK"?> <beans> <bean id="computer" class="lee.Computer"> <property name="name" value="孙悟空的电脑" /> <property name="out" ref="betterPrinter" /> </bean> <bean id="printer" class="lee.Printer" /> <bean id="betterPrinter" class="lee.BetterPrinter" /> <bean id="now" class="java.util.Date" scope="prototype" /> </beans>
|
细心的读者可能已经发现:该配置文件和Spring
配置文件如此相似。实际上这个配置文件只是通过简单修改了Spring
配置文件得到的。上面的配置文件一样配置了computer Bean
,且为该Bean
依赖注入了两个属性:name
和out
除此之外,上面的配置文件中①号代码处还配置了一个prototype
行为的Bean
实例。
本程序中也提供了一个简化的ApplicationContext
接口,该接口仅包含一个getBean()
方法。
1 2 3 4 5 6 7
| package org.crazyit.ioc;
public interface ApplicationContext { Object getBean(String name) throws Exception; }
|
本示例将为该接口提供一个简单的实现类,该实现类就是一个功能强大的工厂。它使用Dom4j
来解析XML
配置文件,并根据配置文件来创建工厂中的Bean
实例。下面是该实现类的代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
| package org.crazyit.ioc;
import java.lang.reflect.*; import java.util.*; import java.io.*; import org.dom4j.*; import org.dom4j.io.*;
public class CrazyitXmlApplicationContext implements ApplicationContext { private Map<String, Object> objPool = Collections .synchronizedMap(new HashMap<String, Object>()); private Document doc; private Element root; public CrazyitXmlApplicationContext(String filePath) throws Exception { SAXReader reader = new SAXReader(); doc = reader.read(new File(filePath)); root = doc.getRootElement(); initPool(); initProp(); }
public Object getBean(String name) throws Exception { Object target = objPool.get(name); if (target.getClass() != String.class) { return target; } else { String clazz = (String) target; return Class.forName(clazz).getConstructor().newInstance(); } } private void initPool() throws Exception { for (Object obj : root.elements()) { Element beanEle = (Element) obj; String beanId = beanEle.attributeValue("id"); String beanClazz = beanEle.attributeValue("class"); String beanScope = beanEle.attributeValue("scope"); if (beanScope == null || beanScope.equals("singleton")) { objPool.put(beanId, Class.forName(beanClazz).getConstructor() .newInstance()); } else { objPool.put(beanId, beanClazz); } } } private void initProp() throws Exception { for (Object obj : root.elements()) { Element beanEle = (Element) obj; String beanId = beanEle.attributeValue("id"); String beanScope = beanEle.attributeValue("scope"); if (beanScope == null || beanScope.equals("singleton")) { Object bean = objPool.get(beanId); for (Object prop : beanEle.elements()) { Element propEle = (Element) prop; String propName = propEle.attributeValue("name"); String propValue = propEle.attributeValue("value"); String propRef = propEle.attributeValue("ref"); String propNameCamelize = propName.substring(0, 1) .toUpperCase() + propName.substring(1, propName.length()); if (propValue != null && propValue.length() > 0) { Method setter = bean.getClass().getMethod( "set" + propNameCamelize, String.class); setter.invoke(bean, propValue); } if (propRef != null && propRef.length() > 0) { Object target = objPool.get(propRef); if (target == null) { } Method setter = null; for (Class<?> superInterface : target.getClass() .getInterfaces()) { try { setter = bean.getClass().getMethod( "set" + propNameCamelize, superInterface); break; } catch (NoSuchMethodException ex) { continue; } } if (setter == null) { setter = bean.getClass().getMethod( "set" + propNameCamelize, target.getClass()); } setter.invoke(bean, target); } } } } } }
|
上面的CrazyitXmlApplicationContext
类当然不能与Spring
的ApplicationContext
实现类相比,该容器类仅仅实现了简单的IoC
功能,而且并未为prototype
行为的Bean
的属性提供依赖注入功能。读者可以通过该工厂类大致了解Spring
底层的实现原理。
下面是测试该工厂类的主类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| package lee;
import org.crazyit.ioc.*;
public class IoCTest { public static void main(String[] args) throws Exception { ApplicationContext ctx = new CrazyitXmlApplicationContext("beans.xml"); Computer c = (Computer) ctx.getBean("computer"); c.keyIn("轻量级Java EE企业应用实战"); c.keyIn("疯狂Java讲义"); c.print(); System.out.println(ctx.getBean("now")); } }
|
从上面程序中的粗体字代码可以看出,本程序的IoC
容器具有和Spring
容器类似的功能,同样可以创建并管理容器中所有的Bean
实例
与简单工厂模式类似的还有工厂方法和抽象工厂模式,下面将进一步讲解工厂方式和抽象工厂模式的设计方式.
原文链接: 9.3.2 简单工厂