学简单工厂设计模式之前,让我们先来了解一下。首先抛出几个问题,什么是接口?接口有什么作用?接口如何使用?
接口是什么?
java中的接口类似于一个抽象类,或者叫它为一个特殊的抽象类,跟一般抽象类相比,接口所有的属性都是公有的,而且为常量,所有的方法都是抽象方法。也就是说,接口里面只有方法的定义,没有方法的实现。
用来干嘛?
通常用接口来定义实现类的外观,也就是定义实现类的行为,用来约束实现类的行为。接口相当于一份契约,根据外部应用需要的功能,约定了实现类应该要实现的功能,但是具体怎么实现并不是接口该关心的事。实现类出了必须实现接口规定的功能以外,也可以根据自身的需要自定义一些方法功能,也就是我们再日常开发中所了解到的,如果实现该接口,就必须就必须添加接口中定义的方法。
通过使用接口,可以实现不相关类的相同行为,而不需要考虑这些类之间的层次关系,接口就是实现类对外的 外观。
接口的思想
根据接口的作用与用途,总结下来就是一句话,“封装隔离”。
隔离就是外部调用与内部实现,外部调用只是通过外部接口进行调用,而外部调用是不知道具体实现的,也就是说外部调用与内部实现是被接口隔离开的。
封装就是对数据的封装,不需要了解其具体的实现,但是这里的封装是指“对被隔离体的行为的封装”,或者是“被隔离体的职责的封装”。
接口的好处
也就是说,接口是不用变的,如果需要实现相应的功能,就在接口实现类里面实现,而不需要修改接口 中的代码。而内部实现的变化就不会影响到外部应用,从而使得系统更加灵活,具有更好的扩展性和可维护性。
接口与抽象类的选择
1.优先选择接口
2.在既要定义子类行为,又要为子类提供公共的功能时应选择抽象类。
面向接口编程
在java程序设计里面,非常讲究分层和模块的划分。通常按照三层来划分java程序,分别为表现层、逻辑层和数据层,这些层之间都是通过接口来通讯。
在每个层里面,又有很多个小模块,每个小模块对外则是一个整体,所以一个模块对外应该提供接口,其他地方需要使用这个模块的功能时,可以通过接口进行调用。
每一个层中,各个模块之间的交互要通过接口。如下图所示:
这里不用去抠细节,各个模块功能是什么?接口里的方法怎么定义?具体怎么实现等。仅仅学习其逻辑结构的设计模式。
上图说提到的组件,就是能够实现一定功能的封装体。小到一个类,大到一个系统,可以叫做组件。
我仔细想一想,小的系统放到大的系统中去,它也只能成为组件了,从设计的角度来看,系统、子系统、模块、组件其实都是一回事的。而且都是接口隔离体,具体如下图所示:
如果不使用设计模式
铺垫的有点长,但是为了更好地理解,如果不使用设计模式,我们怎么写代码?下面是我们的代码部分了。首先其整体思路就是:先创建一个接口类Api,一个接口实现类Impl和一个能够执行的访问的类。Clent。
下面直接上代码更加的明了,废话就少说了。
首先写一个接口
/**
接口类
**/
public interface Api {
public void test(String s);
}
实现类,实现Api接口,作为Api的具体实现类
public class ImplA implements Api {
@Override
public void test(String s) {
System.out.println("ImplA ==" + s);
}
}
定义一个可以访问的客户端,Api
public class Client {
public static void main(String[] args) {
Api api = new ImplA();
api.test("接口隔离原则");
}
}
上面的写法有问题吗?
违反了接口的隔离封装原则,我们不仅仅知道接口,还知道它的实现类,也就是说客户端就不应该知道实现类ImplA。
简单工厂模式看看 。
解决方案-简单工厂模式
简单工厂模式的结构图,如下图所示,在原来的技术上加了一个Factory类,这个类封装了接口的具体实例化。
直接上代码后,更加简洁明了。
1.接口Api的定义:
public interface Api {
public void test(String s);
}
2.定义实现Api接口的实现类ImplA,代码如下:
public class ImplA implements Api {
@Override
public void test(String s) {
System.out.println("ImplA ==" + s);
}
}
3.定义实现Api接口的实现类ImplB,代码如下:
public class ImplB implements Api {
@Override
public void test(String s) {
System.out.println("Impl B" + s);
}
}
4.定义一个封装具体接口实现的工厂类,SimpleFactory,代码具体如下:
public class SimpleFactory {
public static Api createApi(int i) {
Api api = null;
if(i == 1) {
api = new ImplA();
}
else if(i == 2) {
api = new ImplB();
}
return api;
}
}
5.定义一个客户端,通过简单工厂类访问接口,Client类,代码如下:
public class Client {
public static void main(String[] args) {
Api api = SimpleFactory.createApi(1);
api.test("wang");
}
}
简单工厂的优缺点
优点:
1.帮助封装
简单工厂虽然很简单,但是非常友好地帮助我们实现了组件封装,然后让组件外部能真正面向接口编程。
2.解耦
通过简单工厂,实现了客户端和具体实现类的解耦。
如同上面的例子,客户端根本就不知道具体是有谁来实现,也不知道具体是如何实现的,客户端只是通过工厂获取它的需要的接口对象。
缺点:
1.可能增加客户端的复杂
如果通过客户端的参数来选择具体实现类,那么就必须让客户端能理解各个参数所代表的具体功能和意义,这样会增加客户端使用的难度,也部分暴露了内部实现,这种情况可以选择用配置方式实现。
2.不方便展开子工厂
私有化简单工厂的构造方法,使用静态方法来创建接口,也就不能通过写简单工厂类的子类来改变创建接口的行为了。
根据上述缺点所述,每一次添加一个实现类都会修改SimpleFactory类中的代码,这样也可以,但不是一个很好的实现方式,所以下面就用可配置的实现类。如果对于反射一点都不认识的话,建议去学一学反射,再看看代码就更加的易懂一点。
可配置简单工厂模式
1.首先建立一个properties文件。里面就定义了实现类的相对路径,利用反射将,inter.isolation.impl.ImplA创建一个实例对象。
ImplClass=inter.isolation.impl.ImplA
2.工厂类,通过反射创建实现类实例,如下面代码所示。这里踩过一个坑,properties文件需要放在SimpleFactory 类所在的目录下,不然返回空指针异常。
public class SimpleFactory {
/**
* 具体创建Api的方法,根据配置文件的参数来创建接口
* return 返回创建好的实例对象
* @throws IOException
*/
public static Api createApi() throws Exception {
//直接读取配置文件来获取需要创建实例的类
//至于如何读取properties,还有反射这里忽略掉了
Properties properties = new Properties();
InputStream in = null;
in = SimpleFactory.class.getResourceAsStream("Factory.properties");
properties.load(in);
//实例化
Api api = null;
api = (Api)Class.forName(properties.getProperty("ImplClass")).newInstance();
return api;
}
}
最后还有客户端的调用。
public class Client {
public static void main(String[] args) throws Exception {
Api api = SimpleFactory.createApi();
api.test("wang");
}
}
最后想想我们之前遇到配置类,比如数据连接,可以在properties定义一些连接信息,然后就可以在xml文件中调用,用的大概就是这种思路,反射,单例与Properties的解析和IO流。