#0 系列目录#java
#1 场景问题# 你们都知道,在Java应用开发中,要“面向接口编程”。那么什么是接口?接口有什么做用?接口如何使用?一块儿来回顾一下。数据库
##1.1 接口回顾##编程
在Java中接口是一种特殊的抽象类,跟通常的抽象类相比,接口里面的全部方法都是抽象方法,接口里面的全部属性都是常量
。也就是说,接口里面是只有方法定义而不会有任何方法实现。设计模式
一般用接口来定义实现类的外观,也就是实现类的行为定义,用来约束实现类的行为。接口就至关于一份契约,根据外部应用须要的功能,约定了实现类应该要实现的功能
,可是具体的实现类除了实现接口约定的功能外,还能够根据须要实现一些其它的功能,这是容许的,也就是说实现类的功能包含但不只限于接口约束的功能
。api
经过使用接口,能够实现不相关类的相同行为,而不需考虑这些类之间的层次关系,接口就是实现类对外的外观。缓存
根据接口的做用和用途,浓缩下来,接口的思想就是“封装隔离”
。工具
一般提到封装是指对数据的封装,可是这里的封装是指“对被隔离体的行为的封装”,或者是“对被隔离体的职责的封装”;而隔离指的是外部调用和内部实现,外部调用只能经过接口进行调用,而外部调用是不知道内部具体实现的,也就是说外部调用和内部实现是被接口隔离开的
。学习
因为外部调用和内部实现被接口隔离开了,那么只要接口不变,内部实现的变化就不会影响到外部应用
,从而使得系统更灵活,具备更好的扩展性和可维护性,这也就是所谓“接口是系统可插拔性的保证
”这句话的意思。测试
既然接口是一种特殊的抽象类,那么在开发中,什么时候选用接口,什么时候选用抽象类呢?对于它们的选择,在开发中是一个很重要的问题,特别总结两句话给你们:优先选用接口
。在以下状况应选择抽象类:既要定义子类的行为,又要为子类提供公共的功能
。ui
##1.2 面向接口编程## 面向接口编程是Java编程中的一个重要原则
。
在Java 程序设计里面,很是讲究层的划分和模块的划分
。一般按照三层来划分Java程序,分别是表现层、逻辑层、数据层,它们之间都要经过接口来通信。
在每个层里面,又有不少个小模块,一个小模块对外也应该是一个总体,那么一个模块对外也应该提供接口,其它地方须要使用到这个模块的功能,都应该经过此接口来进行调用。这也就是常说的“接口是被其隔离部分的外观
”。基本的三层结构如图1所示:
在一个层内部的各个模块交互也要经过接口,如图所示:
上面频频提到“组件”,那么什么是组件呢?先简单的名词解释一下:
所谓组件:从设计上讲,组件就是能完成必定功能的封装体
。小到一个类,大到一个系统,均可以称为组件,由于一个小系统放到更大的系统里面去,也就当个组件而已。事实上,从设计的角度看,系统、子系统、模块、组件等说的实际上是同一回事情,都是完成必定功能的封装体,只不过功能多少不一样而已。
继续刚才的思路,你们会发现,无论是一层仍是一个模块或者一个组件,都是一个被接口隔离的总体
,那么下面咱们就不去区分它们,统一认为都是接口隔离体便可
,如图所示:
既然在Java中须要面向接口编程,那么在程序中到底如何使用接口,来作到真正的面向接口编程呢?
##1.3 不用模式的解决方案## 回忆一下,之前是如何使用接口的呢,假设有一个接口叫Api,而后有一个实现类Impl实现了它,在客户端怎么用这个接口呢?
一般都是在客户端建立一个Impl的实例,把它赋值给一个Api接口类型的变量,而后客户端就能够经过这个变量来操做接口的功能了,此时具体的结构图如图:
/** * 某个接口(通用的、抽象的、非具体的功能) */ public interface Api { /** * 某个具体的功能方法的定义,用test1来演示一下。 * 这里的功能很简单,把传入的s打印输出便可 * @param s 任意想要打印输出的字符串 */ public void test1(String s); }
/** * 对接口的实现 */ public class Impl implements Api{ public void test1(String s) { System.out.println("Now In Impl. The input s=="+s); } }
/** * 客户端:测试使用Api接口 */ public class Client { public static void main(String[] args) { Api api = new Impl(); api.test1("哈哈,没关系张,只是个测试而已!"); } }
##1.4 有何问题## 上面写得没错吧,在Java的基础知识里面就是这么学的,难道这有什么问题吗?请仔细看位于客户端的下面这句话:
Api api = new Impl();
而后再想一想接口的功能和思想,发现什么了?仔细再想一想?
你会发如今客户端调用的时候,客户端不但知道了接口,同时还知道了具体的实现就是Impl
。而接口的思想是“封装隔离”,而Impl这个实现类,应该是被接口Api封装并同客户端隔离开的
,也就是说,客户端根本就不该该知道具体的实现类是Impl。
有朋友说,那好,我就把Impl从客户端拿掉,让Api真正的对实现进行“封装隔离”,而后咱们仍是面向接口来编程
。但是,新的问题出现了,当他把“new Impl()”去掉事后,发现他没法获得Api接口对象了,怎么办呢?
把这个问题描述一下:**在Java编程中,出现只知接口而不知实现,该怎么办?
**就像如今的Client,它知道要使用Api接口,可是不知由谁实现,也不知道如何实现,从而得不到接口对象,就没法使用接口,该怎么办呢?
#2 解决方案# ##2.1 简单工厂来解决问题## 用来解决上述问题的一个合理的解决方案就是简单工厂,那么什么是简单工厂呢?
分析上面的问题,虽然不能让模块外部知道模块内的具体实现,可是模块内部是能够知道实现类的,并且建立接口是须要具体实现类的
。
那么干脆在模块内部新建一个类,在这个类里面来建立接口,而后把建立好的接口返回给客户端,这样外部应用就只须要根据这个类来获取相应的接口对象,而后就能够操做接口定义的方法了
。把这样的对象称为简单工厂,就叫Factory吧。
这样一来,客户端就能够经过这个Factory来获取须要的接口对象,而后调用接口的方法来实现须要的功能,并且客户端也不用再关心具体实现了。
##2.2 简单工厂结构和说明## 简单工厂的结构如图所示:
Api:定义客户所须要的功能接口
Impl:具体实现Api的实现类,可能会有多个
Factory:工厂,选择合适的实现类来建立Api接口对象
Client:客户端,经过Factory去获取Api接口对象,而后面向Api接口编程
##2.3 简单工厂示例代码##
/** * 接口的定义,该接口能够经过简单工厂来建立 */ public interface Api { /** * 示意,具体的功能方法的定义 * @param s 示意,须要的参数 */ public void operation(String s); }
/** * 接口的具体实现对象A */ public class ImplA implements Api{ public void operation(String s) { //实现功能的代码,示意一下 System.out.println("ImplA s=="+s); } } /** * 接口的具体实现对象B */ public class ImplB implements Api{ public void operation(String s) { //实现功能的代码,示意一下 System.out.println("ImplB s=="+s); } }
/** * 工厂类,用来创造Api对象 */ public class Factory { /** * 具体的创造Api对象的方法 * @param condition 示意,从外部传入的选择条件 * @return 创造好的Api对象 */ public static Api createApi(int condition){ //应该根据某些条件去选择究竟建立哪个具体的实现对象, //这些条件能够从外部传入,也能够从其它途径获取。 //若是只有一个实现,能够省略条件,由于没有选择的必要。 //示意使用条件 Api api = null; if(condition == 1){ api = new ImplA(); }else if(condition == 2){ api = new ImplB(); } return api; } }
/** * 客户端,使用Api接口 */ public class Client { public static void main(String[] args) { //经过简单工厂来获取接口对象 Api api = Factory.createApi(1); api.operation("正在使用简单工厂"); } }
##2.4 使用简单工厂重写示例## 要使用简单工厂来重写前面的示例,主要就是要建立一个简单工厂对象,让简单工厂来负责建立接口对象。而后让客户端经过工厂来获取接口对象,而再也不由客户端本身去建立接口的对象了
。此时系统的结构如图所示:
接口Api和实现类Impl都和前面的示例同样,就不去赘述了。
新建立一个简单工厂的对象,示例代码以下:
/** * 工厂类,用来创造Api对象 */ public class Factory { /** * 具体的创造Api对象的方法 * @return 创造好的Api对象 */ public static Api createApi(){ //因为只有一个实现,就不用条件判断了 return new Impl(); } }
/** * 客户端:测试使用Api接口 */ public class Client { public static void main(String[] args) { //重要改变,没有new Impl()了,取而代之Factory.createApi() Api api = Factory.createApi(); api.test1("哈哈,没关系张,只是个测试而已!"); } }
就如同上面的示例,客户端经过简单工厂建立了一个实现接口的对象,而后面向接口编程,从客户端来看,它根本就不知道具体的实现是什么,也不知道是如何实现的,它只知道经过工厂得到了一个接口对象,而后就能经过这个接口来获取想要的功能
。
事实上,简单工厂能帮助咱们真正开始面向接口编程,像之前的作法,其实只是用到了接口的多态那部分的功能,最重要的“封装隔离性”并无体现出来。
#3 模式讲解# ##3.1 典型疑问## 首先来解决一个常见的疑问:可能有朋友会认为,上面示例中的简单工厂看起来不就是把客户端里面的“new Impl()”移动到简单工厂里面吗?不仍是同样经过new一个实现类来获得接口吗?把“new Impl()”这句话放到客户端和放到简单工厂里面有什么不一样吗?
理解这个问题的重点就在于理解简单工厂所处的位置。
根据前面的学习,咱们知道接口是用来封装隔离具体的实现的,目标就是不要让客户端知道封装体内部的具体实现
。简单工厂的位置是位于封装体内的,也就是简单工厂是跟接口和具体的实如今一块儿的,算是封装体内部的一个类,因此简单工厂知道具体的实现类是没有关系的。整理一下简单工厂的结构图,新的图如图所示:
图中虚线框,就比如是一个组件的包装边界,表示接口、实现类和工厂类组合成了一个组件,在这个封装体里面,只有接口和工厂是对外的,也就是让外部知道并使用的,因此故意漏了一些在虚线框外,而具体的实现类是不对外的,被彻底包含在虚线框内。
对于客户端而言,只是知道了接口Api和简单工厂Factory,经过Factory就能够得到Api了,这样就达到了让Client在不知道具体实现类的状况下获取接口Api。因此看似简单的把“new Impl()”这句话从客户端里面移动到了简单工厂里面,实际上是有了质的变化的。
##3.2 认识简单工厂##
工厂嘛,就是用来造东西的。在Java里面,一般状况下是用来造接口的,可是也能够造抽象类,甚至是一个具体的类实例。必定要注意,虽然前面的示例是利用简单工厂来建立的接口,可是也是能够用简单工厂来建立抽象类或者是普通类的实例的
。
使用简单工厂的时候,一般不用建立简单工厂类的类实例,没有建立实例的必要。所以能够把简单工厂类实现成一个工具类,直接使用静态方法就能够了,也就是说简单工厂的方法一般都是静态的,因此也被称为静态工厂。若是要防止客户端无谓的创造简单工厂实例,还能够把简单工厂的构造方法私有化了。
一个简单工厂能够包含不少用来构造东西的方法,这些方法能够创造不一样的接口、抽象类或者是类实例,一个简单工厂理论上能够构造任何东西,因此又称之为“万能工厂”
。
虽然上面的实例中,在简单工厂里面只有一个方法,但事实上,是能够有不少这样建立方法的,这点要注意。
虽然从理论上讲,简单工厂什么都能造,但对于简单工厂可建立对象的范围,一般不要太大,建议控制在一个独立的组件级别或者一个模块级别,也就是一个组件或模块一个简单工厂
。不然这个简单工厂类会职责不明,有点大杂烩的感受。
类名建议为“模块名称+Factory”,好比:用户模块的工厂就称为:UserFactory
方法名称一般为“get+接口名称”或者是“create+接口名称”,好比:有一个接口名称为UserEbi,那么方法名称一般为:getUserEbi 或者是 createUserEbi。
固然,也有一些朋友习惯于把方法名称命名为“new+接口名称”,好比:newUserEbi,咱们不是很建议
。由于new在Java中表明特定的含义,并且经过简单工厂的方法来获取对象实例,并不必定每次都是要new一个新的实例。若是使用newUserEbi,这会给人错觉,好像每次都是new一个新的实例同样
。
##3.3 简单工厂中方法的写法## 虽说简单工厂的方法可能是用来造接口的,可是仔细分析就会发现,真正能实现功能的是具体的实现类,这些实现类是已经作好的,并非真的靠简单工厂来创造出来的,简单工厂的方法无外乎就是:实现了选择一个合适的实现类来使用
。
因此简单工厂方法的内部主要实现的功能是“选择合适的实现类”来建立实例对象
。既然要实现选择,那么就须要选择的条件或者是选择的参数,选择条件或者是参数的来源一般又有几种:
来源于客户端,由Client来传入参数
来源于配置文件,从配置文件获取用于判断的值
来源于程序运行期的某个值,好比从缓存中获取某个运行期的值
下面来看个示例,看看由客户端来传入参数,如何写简单工厂中的方法。
/** * 对接口的一种实现 */ public class Impl2 implements Api{ public void test1(String s) { System.out.println("Now In Impl The input s=="+s); } }
/** * 工厂类,用来创造Api的 */ public class Factory { /** * 具体的创造Api的方法,根据客户端的参数来建立接口 * @param type 客户端传入的选择创造接口的条件 * @return 创造好的Api对象 */ public static Api createApi(int type){ //这里的type也能够不禁外部传入,而是直接读取配置文件来获取 //为了把注意力放在模式自己上,这里就不去写读取配置文件的代码了 //根据type来进行选择,固然这里的1和2应该作成常量 Api api = null; if(type==1){ api = new Impl(); }else if(type==2){ api = new Impl2(); } return api; } }
public class Client { public static void main(String[] args) { //注意这里传递的参数,修改参数就能够修改行为,试试看吧 Api api = Factory.createApi(2); api.test1("哈哈,没关系张,只是个测试而已!"); } }
要注意这种方法有一个缺点
因为是从客户端在调用工厂的时候,传入选择的参数,这就说明客户端必须知道每一个参数的含义,也须要理解每一个参数对应的功能处理
。这就要求必须在必定程度上,向客户暴露必定的内部实现细节
。
##3.4 可配置的简单工厂## 如今已经学会经过简单工厂来选择具体的实现类了,但是还有问题。好比:在如今的实现中,再新增长一种实现,会怎样呢?
那就须要修改工厂类,才能把新的实现添加到现有系统中。好比如今新加了一个实现Impl3,那么须要相似下面这样来修改工厂类:
public class Factory { public static Api createApi(int type){ Api api = null; if(type==1){ api = new Impl(); }else if(type==2){ api = new Impl2(); } else if(type==3){ api = new Impl3(); } return api; } }
每次新增长一个实现类都来修改工厂类的实现,确定不是一个好的实现方式。那么如今但愿新增长了实现类事后不修改工厂类,该怎么办呢?
一个解决的方法就是使用配置文件,当有了新的实现类事后,只要在配置文件里面配置上新的实现类就行了,在简单工厂的方法里面可使用反射,固然也可使用IoC/DI(控制反转/依赖注入,这个不在这里讨论)来实现。
看看如何使用反射加上配置文件,来实现添加新的实现类事后,无须修改代码,就能把这个新的实现类加入应用中。
ImplClass=cn.javass.dp.simplefactory.example5.Impl
若是新添加了实现类,修改这里的配置就能够了,就不须要修改程序了。
/** * 工厂类,用来创造Api对象 */ public class Factory { /** * 具体的创造Api的方法,根据配置文件的参数来建立接口 * @return 创造好的Api对象 */ public static Api createApi(){ //直接读取配置文件来获取须要建立实例的类 //至于如何读取Properties,还有如何反射这里就不解释了 Properties p = new Properties(); InputStream in = null; try { in = Factory.class.getResourceAsStream("FactoryTest.properties"); p.load(in); } catch (IOException e) { System.out.println("装载工厂配置文件出错了,具体的堆栈信息以下:"); e.printStackTrace(); }finally{ try { in.close(); } catch (IOException e) { e.printStackTrace(); } } //用反射去建立,那些例外处理等完善的工做这里就不作了 Api api = null; try { api = (Api)Class.forName(p.getProperty("ImplClass")).newInstance(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } return api; } }
public class Client { public static void main(String[] args) { Api api = Factory.createApi(); api.test1("哈哈,没关系张,只是个测试而已!"); } }
##3.5 简单工厂的优缺点## **帮助封装:**简单工厂虽然很简单,可是很是友好的帮助咱们实现了组件的封装,而后让组件外部能真正面向接口编程。
**解耦:**经过简单工厂,实现了客户端和具体实现类的解耦。如同上面的例子,客户端根本就不知道具体是由谁来实现,也不知道具体是如何实现的,客户端只是经过工厂获取它须要的接口对象。
**可能增长客户端的复杂度:**若是经过客户端的参数来选择具体的实现类,那么就必须让客户端能理解各个参数所表明的具体功能和含义,这会增长客户端使用的难度,也部分暴露了内部实现,这种状况能够选用可配置的方式来实现。
**不方便扩展子工厂:**私有化简单工厂的构造方法,使用静态方法来建立接口,也就不能经过写简单工厂类的子类来改变建立接口的方法的行为了。不过,一般状况下是不须要为简单工厂建立子类的。
##3.6 思考简单工厂##
简单工厂的本质是:选择实现。
注意简单工厂的重点在选择,实现是已经作好了的。就算实现再简单,也要由具体的实现类来实现,而不是在简单工厂里面来实现。简单工厂的目的在于为客户端来选择相应的实现
,从而使得客户端和实现之间解耦,这样一来,具体实现发生了变化,就不用变更客户端了,这个变化会被简单工厂吸取和屏蔽掉。
实现简单工厂的难点就在于“如何选择”实现
,前面讲到了几种传递参数的方法,那都是静态的参数,还能够实现成为动态的参数。好比:在运行期间,由工厂去读取某个内存的值,或者是去读取数据库中的值,而后根据这个值来选择具体的实现等等。
建议在以下状况中,选用简单工厂:若是想要彻底封装隔离具体实现,让外部只能经过接口来操做封装体
,那么能够选用简单工厂,让客户端经过工厂来获取相应的接口,而无需关心具体实现;若是想要把对外建立对象的职责集中管理和控制
,能够选用简单工厂,一个简单工厂能够建立不少的、不相关的对象,能够把对外建立对象的职责集中到一个简单工厂来,从而实现集中管理和控制。
##3.7 相关模式##
简单工厂是用来选择实现的
,能够选择任意接口的实现,一个简单工厂能够有多个用于选择并建立对象的方法,多个方法建立的对象能够有关系也能够没有关系。
抽象工厂模式是用来选择产品簇的实现的
,也就是说通常抽象工厂里面有多个用于选择并建立对象的方法,可是这些方法所建立的对象之间一般是有关系的,这些被建立的对象一般是构成一个产品簇所须要的部件对象。
因此从某种意义上来讲,简单工厂和抽象工厂是相似的,若是抽象工厂退化成为只有一个实现,不分层次,那么就至关于简单工厂了
。
简单工厂和工厂方法模式
简单工厂和工厂方法模式也是很是相似的。工厂方法的本质也是用来选择实现的,跟简单工厂的区别在于工厂方法是把选择具体实现的功能延迟到子类去实现
。若是把工厂方法中选择的实现放到父类直接实现,那就等同于简单工厂。
简单工厂和能建立对象实例的模式
简单工厂的本质是选择实现,因此它能够跟其它任何可以具体的建立对象实例的模式配合使用,好比:单例模式、原型模式、生成器模式等等。