文章开头首先很是感谢各位的支持,代理模式中提到了class文件(即字节码文件)的相关知识,有一位读者说想要看有关class文件的相关内容,我也意识到了这一点,因此之后若是有在讲解那个模式的过程中用到了其它的技术,我会留一些篇幅去介绍这个技术,有关class文件的内容我也会看之后的模式当中有没有用到的地方顺便简单介绍一下,若是没有的话,我会在设计模式介绍完之后专门写一篇有关java字节码文件的相关内容。java
本章咱们继续讨论新的设计模式,工厂方式模式,在这以前,LZ决定先给出引自其它地方的标准定义以及类图。mysql
定义:工厂方法(Factory Method)模式的意义是定义一个建立产品对象的工厂接口,将实际建立工做推迟到子类当中。核心工厂类再也不负责产品的建立,这样核心类成为一个抽象工厂角色,仅负责具体工厂子类必须实现的接口,这样进一步抽象化的好处是使得工厂方法模式可使系统在不修改具体工厂角色的状况下引进新的产品。sql
能够看到工厂方法模式中定义了一个工厂接口,而具体的建立工做推迟到具体的工厂类,它是对简单工厂模式中的工厂类进一步抽象化,从而产生一个工厂类的抽象和实现体系,从而弥补简单工厂模式对修改开放的诟病。数据库
下面LZ给出工厂方法模式的类图,该类图和定义引自百度百科。编程
能够看到,上面右半部分是产品抽象和实现体系,左半部分是工厂抽象和实现体系,其中工厂体系依赖于产品体系,每个工厂负责创造一种产品,这就省去了简单工厂中的elseif判断,又客户端决定实例化一个特定的工厂去建立相应的产品。设计模式
下面LZ简单的使用JAVA代码诠释上述标准的工厂方法模式的类图。架构
首先是抽象产品接口。oracle
publicinterface Light { public void turnOn(); public void turnOff(); }
下面是具体的产品。app
public class BuldLight implements Light{ public void turnOn() { System.out.println("BuldLight On"); } public void turnOff() { System.out.println("BuldLight Off"); } }
public class TubeLight implements Light{ public void turnOn() { System.out.println("TubeLight On"); } public void turnOff() { System.out.println("TubeLight Off"); } }
下面是抽象的工厂接口。框架
public interface Creator { public Light createLight(); }
下面是建立指定产品的具体工厂。
public class BuldCreator implements Creator{ public Light createLight() { return new BuldLight(); } }
public class TubeCreator implements Creator{ public Light createLight() { retur nnew TubeLight(); } }
下面咱们写个测试类去实验一下这个工厂方法模式的实例代码。
public class Client { public static void main(String[] args) { Creator creator = new BuldCreator(); Light light = creator.createLight(); light.turnOn(); light.turnOff(); creator = new TubeCreator(); light = creator.createLight(); light.turnOn(); light.turnOff(); } }
运行结果以下。
能够看到,咱们使用能够随意的在具体的工厂和产品之间切换,而且不须要修改任何代码,就可让原来的程序正常运行,这也是工厂方法模式对扩展开放的表现,另外工厂方法模式弥补了简单工厂模式不知足开闭原则的诟病,当咱们须要增长产品时,只须要增长相应的产品和工厂类,而不须要修改现有的代码。 上面的示例能够比较清楚的展现各个类之间的关系,可是始终缺少说服力,由于它彻底没有什么实际意义,下面LZ就给出一些咱们接触过的例子来讲明工厂方法模式的好处。 关于可以说明工厂方法模式的实例,LZ翻遍了全部能找到的源码,想寻找一个让各位读者既能学习到新的东西,又能对工厂方法理解更深的现有的优秀框架的设计。通过跋山涉水,LZ决定仍是拿数据库链接来讲事,我知道你想说,我去,又是数据库链接。LZ只想说,咱们天天作的最多的就是增删改查好吗,其它的咱也不认识啊,囧。 众所周知,为了统一各个数据库操做的标准,因而有了JDBC的API,它用于给咱们这种被称做只会使用现成的东西的程序猿,提供一系列统一的,标准化的操做数据库的接口。其实JDBC的各个类或接口,就是咱们操做数据库的过程当中各个协助者的抽象,这样的设计是为了让咱们对数据库的操做依赖于抽象,还记得咱们在设计模式总纲中提到的一句话吗,用抽象构建框架,用细节扩展实现。
JDBC API(即抽象的接口或类)就是整个数据库操做的框架,而各个数据库的驱动就是那些细节。而咱们的操做依赖于JDBC API,而不是任何一个具体数据库的细节。
JDBC是如何统一了数据库世界的呢?其实最主要的就是靠两个接口,就统一了世界。。。
来看第一个接口Driver,附上源码。
package java.sql; import java.sql.DriverPropertyInfo; import java.sql.SQLException; public interface Driver { Connection connect(String url, java.util.Properties info) throws SQLException; boolean acceptsURL(String url) throws SQLException; DriverPropertyInfo[] getPropertyInfo(String url, java.util.Properties info)throws SQLException; int getMajorVersion(); int getMinorVersion(); boolean jdbcCompliant(); }
因为篇幅,LZ删掉了不少注释,只保留了这个类注释的第一句话,翻译过来是这是一个任何驱动类都必须实现的接口。多么霸气啊。也就是每一个数据库厂商都必须实现这个接口来提供JDBC服务,即java数据库链接服务,来方便程序猿对数据库应用编程。
咱们先忽略掉下面的五个方法,第一个方法毫无疑问是这个接口中相对而讲最重要的方法了,即创造一个数据库链接,虽然方法名称是connect,可是我以为这个方法彻底能够改成createConnection。
提到Connction,这个接口咱们必定不陌生,它的源码也已经在代理模式一章出现过,这里咱们再次让它出场,我依旧会删掉它的大部分方法,限于篇幅。
package java.sql; import java.sql.PreparedStatement; import java.sql.SQLException; public interface Connection extends Wrapper { Statement createStatement() throws SQLException; PreparedStatement prepareStatement(String sql) throws SQLException; }
以上即是Connection接口,这里只留下了两个方法,这两个方法相信各位读者都很是熟悉,它们都是咱们最常常用的方法之二。
以上两个接口做为JDBC API的一部分,它们至关于告诉了数据库生产厂商两个要求。
第一,数据库厂商要提供一个数据库驱动类,它的做用能够是能够创造数据库链接,而这个数据库链接向上转型为咱们JDBC的Connection。
** 第二,数据库厂商要提供一个数据库链接的实现类,这个实现类能够执行具体数据库的各个操做,好比帮咱们执行SQL,返回执行结果,关闭链接等等。**
咱们都知道mysql的驱动类位于com.mysql.jdbc.Driver,而mysql的connection实现类也在这个包中,名称是ConnectionImpl,而相应的oracle也有驱动类,位于oracle.jdbc.driver.OracleDriver,相应的oracle也有connection实现类,位于oracle.jdbc.OracleConnectionWrapper。通常每一个数据库都会有一个Connection的扩展接口,这个接口的做用是提供使用者针对当前数据库特殊的操做。
这里咱们忽略掉这些中间接口以及抽象类,我给出上述六个类的UML图,若是各位之前知道工厂方法模式的话,各位看一下,它们的关系是否很熟悉。
咱们对比上面标准的工厂方法模式,就会发现它们的关系不正是工厂方法模式吗?
工厂方法模式就是提供一个抽象的工厂,一个抽象的产品,在上述当中至关于Driver(数据库链接工厂)和Connection(抽象产品),实现的一方须要提供一个具体的工厂类(好比mysql驱动)和一个具体的产品(好比mysql数据库链接)。
客户端调用时不依赖于具体工厂和产品(即究竟是mysql驱动,mysql数据库链接仍是oracle驱动,oracle链接,咱们程序猿不须要管的,咱们只管使用抽象的driver和connection,对吧?),而是依赖于抽象工厂和抽象产品完成工做。
各位能够看到我在类图里面加入了一个DriverManager,这个类相信各位也不陌生,这是咱们每天打交道的类,虽然说由于hibernate和ibatis的封装,或许咱们不能常常看到,但LZ相信它活在每一个程序猿的心中。
DriverMananger在这个设计当中扮演者一个管理者的角色,它帮咱们管理数据库驱动,让咱们不须要直接接触驱动接口,咱们获取链接只须要和DriverManager打交道就能够,也就是说客户端依赖于DriverManager和Connection就能够完成工做,再也不须要与Driver关联,因此上述说咱们依赖于Driver和Connection,如今DriverManager帮咱们管理Driver,那咱们只须要依赖于DriverManager和Connection就能够了。
LZ在类图中拉出了DriverManager的方法,其中的registerDriver方法正是咱们注册数据库驱动的入口。来看看mysql的Driver中作了什么,oracle相似。
public class Driver extends NonRegisteringDriver implements java.sql.Driver{ public Driver() throws SQLException{} static{ try{ DriverManager.registerDriver(new Driver()); } catch (SQLException E) { throw new RuntimeException("Can't register driver!"); } } }
能够看到,在类构造方法中,加入了registerDriver这个方法,因此当咱们使用class.forName加载驱动的时候,将会把mysql驱动注册到DriverManager,这时DriverManager中就会持有Mysql驱动所必要的信息,咱们就可使用DriverManager来得到具体的mysql链接了,固然,你要提供url,用户名和密码。
原来咱们都是活在温室里的花朵,都被这些设计者细心呵护着,生怕咱们知道一点底层的东西。记得LZ当初第一次看到Class.forName时,还以为真是个神奇的东西,没想到只是这些设计者给咱们的糖外衣。
工厂方法模式的好处和适用的场景都相对比较好理解。
好处就是,从类关系上来讲,它可让客户端与具体的工厂与产品解耦,从业务角度来讲,它让客户端与具体的产品解耦。
适用的场景就是咱们须要一个产品帮咱们完成一项任务,可是这个产品有可能有不少品牌(像这里的mysql,oracle),为了保持咱们对产品操做的一致性,咱们就可能要用到工厂方法模式。
工厂方法模式也有它所不足的地方,可能你会说,这多好啊,咱们操纵数据库再也不须要关心具体是哪一个数据库。是的,你很爽啊,那是由于这些产品的实现都不用你写啊,都是数据库厂商给你写的。
假设产品数量巨多,并且须要咱们亲手去逐个实现的时候,工厂方法模式就会增长系统的复杂性,处处都是工厂类和产品类,并且这里所说的工厂类和产品类只是概念上的,真正的产品可能不是一两个类就能搞定,不然mysql和oracle的驱动包为啥要那么多类,而不是就一个Driver和一个Connection。
固然这也不是绝对,好比咱们常用的HashSet和ArrayList,也是使用的工厂方法模式,各位看下他们的类图就看出来了。
各位可能会说,不对啊,这和咱们刚才理解的不太同样啊,按照刚才的说法,咱们不是应该直接使用iterable和iterator吗?这样多牛X,咱们不依赖于具体产品了。对于这个LZ表示三条黑线垂下,sun或者说oracle为了集合框架给你提供了这么多具有各个特性的集合,你只用iterator和iterable,估计当初参与设计集合框架的人都要气的去shi了。。
上述这即是工厂方法模式另一种用法了,刚才由于咱们不关心真正的产品是什么,因此咱们直接使用抽象接口操做。可是咱们使用iterable和iterator的时候,咱们是关心真正产品的特性的,因此为了使用产品的特性,咱们就须要使用产品特有的接口了,好比特殊的SortedSet可排序,好比ArrayList能够有重复元素,能够根据索引获取元素等等。固然你依然是可使用iterable和iterator的,可是无论你用什么,在这种场景下,产品是你本身选的,一句话,你随便。。。
两种使用方式一种是对使用者透明的,一种是不透明的,一种是使用者对具体的产品不关心,这种状况下,通常产品提供的功能是相似的。一种是使用者很是了解产品的特性,并想使用产品的特性,这种状况下,通常产品只提供最基本的一致的功能,但每一个产品都会有本身独特的一面。
可是LZ我的以为真正作项目的过程中不多用到工厂方法模式,这个模式更多的是帮助咱们理解现有的开源项目,就像如今,你是否是对JDBC的大致框架有了必定认识了呢,若是你不知道这个模式,可能看源码会以为一头雾水呢。
另外,文章最后插播一段内容,若是各位看过上一章(简单工厂模式)的话,必定还记得那个恶心的elseif结构,这是简单工厂的诟病,它对扩展开放,对修改也开放。
简单工厂模式在项目规模相对较小或者说具体的产品类相对很少的状况下(针对上章的描述,特指的servlet数量很少的状况下),其实这种设计仍是能够接受的,由于少许的elseif能够换来咱们开发上的便利。
因此LZ建议各位永远不要忘记,规则只是用来指导你的,不是用来限制你的,只要设计合理,你的设计就是规则!
不过针对简单工厂模式,你能够认为它给咱们提供了一个思路,就是咱们其实能够省掉那些让人痛恨的xml配置,对于咱们后续的优化有着必定指导意义。
就像上一章中的处理方式,很明显存在着隐患,那就是在servlet数量急剧上升的时候,工厂类就会变得很是臃肿和复杂,变得难以维护和阅读。本章LZ给各位读者介绍一种优化方式,能够采起一项JDK当中在1.5版本引入的技术,即注解,去消除那些elseif的逻辑判断。
咱们能够参考struts2的作法,即每个Servlet咱们均可以采用注解去设置它的名称,或者叫url,而后咱们让咱们的简单工厂依据这个去实例化咱们的servlet。
根据以上方案,咱们须要按照如下步骤让咱们的简单工厂完全死翘翘。
1.须要声明一个注解,它能够用来给servlet标识它的名称。
2.须要声明一个注解的处理器,用来处理咱们的注解,主要做用是经过一个CLASS文件,去得到它的注解信息。
3.基于性能,咱们须要将servlet与名称的映射与应用的生命周期绑定,而且这份映射在整个应用当中有且仅有一份,且不可更改。
4.让咱们用于分派请求的过滤器,使用映射信息将客户请求对应到相应的servlet去处理,而且将分派逻辑移回过滤器,从而完全删除简单工厂,即ServletFactory。
特别说一下,这四步当中,其中第三步是可选的,但也是必须的,由于若是不作这种处理,那么你就等着你的项目N长时间打开一个网页吧。
以上是简单工厂给咱们的启示,具体如何实现这样一个基于注解的请求分配的架构,LZ再也不给各位一一演示,由于这已经只剩下一个堆积代码的过程,具体的实现方案已经有了,若是各位读者有兴趣,能够私底下尝试一下这种方式。
好了,工厂方法模式就给各位分享到这吧,感谢各位的欣赏。
下期预告,能不能取消这个预告。。。