详解Java从工厂方法模式到 IOC/DI思想
简单工厂的本质是选择实现,说白了是由一个专门的类去负责生产我们所需要的对象,从而将对象的创建从代码中剥离出来,实现松耦合。我们来看一个例子:
我们要创建一个文件导出工具
public interface FileOper{ public Boolean exceptFile(String data);}public class XMLFileOp implment FileOper{ public Boolean exceptFile(String data){ System.out.println('导出一个xml文件'); return true; } }public class Factory{ public static FileOper createFileOp(){return new XMLFileOp(); } }public Class Test{ public static void main(String args[]){ FileOper op = Factory.createFileOp(); op.exceptFile('测试');}}
这样看起来没什么问题,那么我们既然做出来了这个结构,就是为了后续的扩展它,例子中只是为了实现XML文件的导出,后续,我们可以自己实现一个txt文件的导出类,只需要实现FileOper接口就好:
public Class TxtFileOp implment FileOper{ public Boolean ExceptFile(String data){ System.out.println('导出txt文件'); return true; } }
这时候我们还是通过工厂来获取这个对象,只需将Factory中追加一个else if 即可通过传参来获取想要的对象了。
工厂方法模式仔细分析上面的场景,事实上在实现导出文件的业务逻辑中,它根本不知道要使用哪一种导出文件的格式,因此这个对象根本就不应该和具体导出文件的对象耦合在一起,它只需要面向导出文件接口(FileOper)就好,这是工厂的思想,我们上面用简单工厂没错啊,但是后面又加入了新的扩展
这样一来,又有新的问题,面对新的类,简单工厂便不能提供动态的扩展,必须要去修改内部的代码,破坏了开闭原则。我们上一篇也提到了,简单工厂也有它自身的缺陷,其中最严重的就是,它虽然对依赖对象的主体实现了解耦,可是它本身内部却耦合较为严重。这时候我们可以看看工厂方法模式了,工厂方法模式的思路很有意思:老子不管了!采取无为而治的方式。不是需要接口对象么,那就定义一个方法来创建,可是事实上它自己是不知道如何创建这个接口对象的,不过这不重要,定义成抽象方法就行了,交给子类去实现,老子欠债,儿子你来还。
工厂方法的结构Product:工厂方法所创建的具体对象的统一接口。
Factory:为该类产品的抽象工厂,其内部有声明的工厂方法,工厂方法多为抽象方法,且返回一个Product对象
ProductOne:为具体的产品,也就是Product的具体实现类,真正的工厂产物。
SpecificFactory:具体的工厂,用于生产指定类型的Product,例如图中它只负责生产 ProductOne这个对象。
工厂方法模式的样例代码public class AbstractFactoryTest { public static void main(String[] args) {try { Product a; AbstractFactory af; af = (AbstractFactory) ReadXML1.getObject(); a = af.newProduct(); a.show();} catch (Exception e) { System.out.println(e.getMessage());} }}//抽象产品:提供了产品的接口interface Product { public void show();}//具体产品1:实现抽象产品中的抽象方法class ConcreteProduct1 implements Product { public void show() {System.out.println('具体产品1显示...'); }}//具体产品2:实现抽象产品中的抽象方法class ConcreteProduct2 implements Product { public void show() {System.out.println('具体产品2显示...'); }}//抽象工厂:提供了厂品的生成方法interface AbstractFactory { public Product newProduct();}//具体工厂1:实现了厂品的生成方法class ConcreteFactory1 implements AbstractFactory { public Product newProduct() {System.out.println('具体工厂1生成-->具体产品1...');return new ConcreteProduct1(); }}//具体工厂2:实现了厂品的生成方法class ConcreteFactory2 implements AbstractFactory { public Product newProduct() {System.out.println('具体工厂2生成-->具体产品2...');return new ConcreteProduct2(); }}
基于XML解析的外部配置文件
class ReadXML1 { //该方法用于从XML配置文件中提取具体类类名,并返回一个实例对象 public static Object getObject() {try { //创建文档对象 DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = dFactory.newDocumentBuilder(); Document doc; doc = builder.parse(new File('src/FactoryMethod/config1.xml')); //获取包含类名的文本节点 NodeList nl = doc.getElementsByTagName('className'); Node classNode = nl.item(0).getFirstChild(); String cName = 'FactoryMethod.' + classNode.getNodeValue(); //System.out.println('新类名:'+cName); //通过类名生成实例对象并将其返回 Class<?> c = Class.forName(cName); Object obj = c.newInstance(); return obj;} catch (Exception e) { e.printStackTrace(); return null;} }}
上面是一个较为初级也较为经典的工厂方法模板,工厂方法还有另一种用法,即抽象的工厂父类除了创建对象的方法之外,还包含其他的一些方法,而工厂父类通常使用这些方法来完成某些任务,下面我们来看看这第二种表现方式。
工厂方法模式实现文件导出根据上面工厂方法模式提供的思路,我们重新来思考并实现一下一开始那个文件导出的功能。
/** * 文件导出接口 * 实现将指定数据的导出 * 扩展:实现该接口,可指定生产具体文件类型 * @author GCC */public interface ExportFileApi { /** * 导出文件 * @param data 待导出数据 * @return boolean */ boolean exportFile(String data);}/** * 生产Excel文件导出器 * @author GCC */public class ExportExcelFile implements ExportFileApi { @Override public boolean exportFile(String data) {//todo 处理数据return false; }}/** * 导出功能口,工厂 * @author GCC */public abstract class ExportFileOperate { public Logger logger = Logger.getLogger(ExportFileOperate.class); //使用产品对象来实现一定功能的方法,这里是实现数据导出 public void export(String data){ExportFileApi exportoper = methodFactory();if(exportoper.exportFile(data)){ logger.info('文件导出成功'); return;}logger.error('文件导出失败'); } //工厂方法 protected abstract ExportFileApi;}/** * 将指定数据导出为Excel文件 * @author GCC */public class ExportExcelFileFactory extends ExportFileOperate { @Override protected ExportFileApi methodFactory() {return new ExportExcelFile(); }}/** * 客户用例 */public class App { static Logger logger = Logger.getLogger(App.class); public static void main( String[] args ) {ExportFileOperate ex = new ExportExcelFileFactory();ex.export('测试数据'); }}
这里大家可能会 有疑惑,你这个实现方式怎么和前面提到的工厂方法模式的标准样例不一样? 其实这是工厂方法模式的另一种结构,确切地说这才是真正意义上的工厂方法模式(上面的模板只是工厂方法的正常形态)。
这一种的实现方式是 :父类会是一个抽象类,里面包含创建所需对象的抽象方法(代码样例中ExportFileOperat类的methodFactory()方法,这里ExportFileOperat类就是所谓的工厂类,需要补充的是设计模型的使用,不要拘泥于命名名称,可以根据实际需求来进行相应的变化),这些抽象方法就是工厂方法模式中的工厂方法。父类里面,通常会有使用这些产品对象来实现一定的功能的方法(代码样例中ExportFileOperat类的export()方法)。而这些方法所实现的功能通常都是公共功能,不管子类选择了何种具体的产品实现,这些方法总能正常运行。
之所以会有上面两种方式,主要原因在于工厂方法对于客户端的支持,这里需要弄清楚一个问题,谁在使用工厂方法所创建的对象?
事实上,在工厂方法模式里,应该是工厂中的其他方法来使用工厂所创建的对象,为了方便,工厂方法创建的对象也可直接提供给外部的客户端来调用,但工厂方法的本意是由Factory抽象父类内部的方法来使用工厂方法创建的对象。
以下这幅时序图,即说明了客户端调用factory的两种方式。
其实客户端应该使用Factory对象,或者是由Factory所创建出来的产品对象,对于客户端使用Factory对象,这个时候工厂方法创建的对象,是Factory中的某些方法在用,对于使用那些由Factory创建出来的对象,这个时候工厂方法创建的对象,是构成客户端所需对象的一部分。
工厂方法与简单工厂的区别下面我们看一个例子,这里我打算做个计算器,如果用简单工厂模式来做,它的结构是这样的:
public class SimpleFactory { public Calculator create(String type){switch (type){ case '+'://返回一个具有加法功能的计算器对象return new AddCalculator(); case '-'://返回一个具有减法功能的计算器对象return new DeCalculator(); default:return null;} }}
为了工厂更完整,采用传参的静态工厂方式来实现,这样我简单工厂里将通过Switch语句来管控生产哪一种计算类,这时候,突然来了新的需求,我需要一个乘法的功能,这时候我就得实现计算器接口,完成一个乘法类,同时去简单工厂的代码里,追加一个case。
同理,我使用工厂方法的模式来做这个功能,这块的类图则如上图一样,我的工厂代码里不需要Switch了,只需要一个具有生产计算器对象的抽象方法的抽象工厂类即可,当我需要一个乘法能力的计算器时,实现计算器接口,完成乘法类,然后继承抽象工厂,完成一个乘法的工厂子类,然后再用乘法的工厂子类来创建乘法类。然后再去修改客户端。
上面一对比,嘿,这升级版的工厂方法怎么比简单工厂还复杂了!?肯定很多同学在看工厂设计模式的时候很困惑,简单工厂和工厂方法的区别在哪,明明感觉用简单工厂更方便呢?
其实回头好好看看设计原则,就会发现,这是一个解耦的过程,简单工厂模式最大的优点在于工厂类中包含了必要的逻辑判断,根据客户的选择动态的实例化相关的产品类,对于客户端来说,除去了与具体产品对象的依赖。但问题就是随着你的新需求,如果使用简单工厂,那么你就不得不去破坏开闭原则,而看起来改动更为复杂的工厂方法模式,你并不需要对以前的代码进行改动,只需要继承,扩展即可。仔细观察一下,简单工厂是让客户端与依赖对象进行解耦,而工厂方式模式又是对工厂的一层解耦,原本内部耦合性较强的if else,变成了由客户端或者配置文件来控制,工厂方法将简单工厂内部的逻辑判断移到了使用它的外部(客户端或者配置文件)来控制。本来扩展是需要修改工厂类源代码的,现在变成了客户端修改调用或者配置文件中的一个参数。
工厂方法模式的意义工厂方法模式的主要思想是让父类在不知情的情况下,完成自身功能的调用,而具体的实现则延迟到子类来做;或者说是在静态工厂中,将其原本耦合的if else抽离出来,配合配置文档使用,将写死的if else灵活实现(配置文件并不是默认必须要有的)。这样在设计的时候,不用去考虑具体的实现,需要某个对象,把它通过工厂方法返回就好,在使用这些对象实现功能的时候还是通过接口来操作,这里就有一点IOC的韵味了。
工厂方法模式与IOC、DI什么是IOC/DI?想想之前没有学习设计模式,刚学会使用Java就被Spring的bean配置文件支配的恐惧。
那时候你说自己学Java,对方一定会问你Spring,说到Spring,肯定避不开“什么是IOC,什么是DI?”这个让人头痛的问题,那么,到底什么是IOC,DI?
看完工厂的设计思想,对这个问题才开始了真正的思考。
IOC——控制反转
DI——依赖注入
除了上面脱口而出的回答,我想我们这些面向对象的程序猿们,应该有个更深入的理解,到底什么是控制反转,什么是依赖注入。要想理解上面两个概念,必须把问题拆开来看,先搞清楚基本的问题几个问题:
主客体是谁,或者说参与这个概念的都有谁?
什么叫依赖?为什么会有依赖?
什么叫注入?注入的是什么?谁注入谁?
控制反转,谁控制谁,控制的是什么,既然叫反转,正转是啥?
下面我们一个个来解决问题:
1、参与者,说起参与者,一般我们在这个概念中是有三个参与者,具体某个类,容器,某个对象所依赖的外部资源(另一个对象),就好比我有三个类,A,B,C,A对象我们把它想象成一个客户端,B是它需要的一个外部的资源,C是一个叫容器的第三方。
2、什么叫依赖,这个就比较好说了,你有一个A类,但是你A类的成员变量有一个是B类的实例声明,那么A就依赖于B,也就是说,A如果想正常运转(或者说功能正常),必须得依赖于它的成员变量B,至于为什么会有依赖,那也好理解了,面向对象就是将功能封装,每个对象都功能单一,这样有些复杂的对象需要实现复杂的功能,就必须需要其他类的协同。
3、注入,就是说,A你的成员变量B只是声明了一个变量,它对于对象A来说,只是一个引用,一个字符,本身并没有实体,你可以new 一下这个变量的构造函数,才能使这个变量真正有灵魂,又或者用它来承接外部传进来的同类实体,这里外部传进来B的方式就叫注入,注入的是这个变量类型 具体的实例化对象。谁来注入,当然是容器C来注入给A,将B注入给A
4、简单来说就是容器来控制A,控制的就是A所依赖的对象B实例的创建,反转是与正转对应来说的,什么是正转呢,A类中有个对象B的成员变量,正常情况下,A类中功能用到B对象的时候,A要主动去获取一个B对象,例如new一下,这种情况被称为正向的。这样就比较好理解反转了,A不再去主动获取B对象了,而是被动的等待B的到来(注入),等待容器C获取一个B的实例,然后反向的注入进A。
所以,综上来看,控制反转和依赖注入其实说的是同一件事,说白了就是对象创建这个责任归谁的问题,依赖注入是从应用程序的角度去描述,应用程序依赖外部容器去创建并注入它所需要的外部资源对象。控制反转是从容器的角度出发,容器控制应用程序,由容器反向地向应用程序注入其所需要的外部对象。
其实IOC/DI并不是一种代码实现,更多的它是一种思想,它从思想上完成了 “主从换位” 的变化,应用程序本来是主体,占绝对地位,它需要什么都会主动出击去获取,过强的控制欲导致了它耦合过重,而在IOC/DI思想中,应用程序变成被动的等待容器的注入,需要啥只能提出来,什么时候给,给什么样子的,主动权完全交给了容器,较强的实现了解耦,程序的灵活性也就高了
工厂方法与IOC/DI思想从某个角度来看,工厂方法模式跟IOC/DI的思想很贴近。
上面也说过了,IOC/DI就是让应用程序不再主动获取外部资源,而是被动等待第三方的注入,那么在编写程序的时候,一旦遇到需要外部资源的地方,就会开一个窗口,提供给容器一个注入的途径,让容器注入进来,细节这里就不过多赘述了,自己去找Spring聊吧。 下面用IOC/DI和工厂方法来实现一个样例对比一下。
用IOC/DI来实现一个类Person:
public class Person { private String name; //依赖文件操作对象 private FileUtil fileUtil; private int age; //提供set方法,供外部注入 public void setFileUtil(FileUtil fileUtil){this.fileUtil = fileUtil; }public void opFile(String fileurl){fileUtil.createFile(fileurl); } }
这就是IOC/DI思想来实现的一个类,我依赖FileUtil,没事,我不管,我提供给你一个set的注入途径,剩下的我不管了,我就默认我用FileUtil的时候,它是真真切切存在在堆中的对象。(本质是当外部的容器,创建Person对象的时候,会发现它依赖FileUtil,然后容器去获取一个FileUtil,通过Person提供的Set方法,将获取的FileUtil对象注入进去,然后一个完整的Person对象就被制造出来了,这个过程Person角度来看,Person是无感的)
下面用工厂方法来搞上面的例子:
public abstract class Person { private String name; private int age; //交给子类去实现我的依赖 public abstract FileUtil getFileutil(); public void opFile(String fileurl){getFileutil().createFile(fileurl); }}
这里,Person类也是需要用到FileUtil,但是它也不需要主动的去new一下FileUtil,而是通过抽象方法的形式,将FileUtil的实例化延申到子类去实现,其实就是变相地提供了一种注入渠道(标准bean中是通过set方法,容器调用Set方法注入需要的对象,而工厂方法则是通过实现子类,即让子类来完成依赖对象的注入)
仔细体会这两种写法,对比他们的实现,在思想层面来看,会发现,工厂方法模式和IOC/DI的思想是相似的,都是“主动变被动”,“主位换从位”,从而获得了更加灵活的程序结构。
以上就是详解Java从工厂方法模式到 IOC/DI思想的详细内容,更多关于Java从工厂方法模式到 IOC/DI思想的资料请关注好吧啦网其它相关文章!
相关文章: