摘要
JDK:1.8.0_202
# 一:问题
看看下面基本的接口和实现
接口:(Api.java)
public interface Api {
void print(String s);
}
2
3
实现类:(ApiImpl.java)
public class ApiImpl implements Api {
public void print(String s) {
System.out.println("Now In Impl. The input s==" + s);
}
}
2
3
4
5
客户端:(Client1.java)
public class Client1 {
public static void main(String[] args) {
Api api = new ApiImpl();
api.print("测试"); // Now In Impl. The input s==测试
}
}
2
3
4
5
6
问题:
仔细看位于客户端的这句话:Api api = new ApiImpl();
你就会发现在客户端调用时,客户端不但知道了接口,同时还知道了具体的实现就是ApiImpl。而接口的思想是 "封装隔离",而Impl这个实现类,应该是被接口Api封装并同客户端隔离开的。也就是说,客户端根本就不应该知道具体的实现类是ApiImpl。
有人说,那好,就把Impl从客户端拿掉,让Api真正的对实现进行 "封装隔离",然后我们还是面向接口来编程。可是,新的问题出现了,当他把 "new ApiImpl()" 去掉过后,发现他无法得到Api接口对象了,怎么办呢?
把这个问题描述一下:
在Java编程中,出现只知接口而不知实现,该怎么办?
就像现在的Client,它知道要使用Api接口,但是不知由谁实现,也不知道如何实现,从而得不到接口对象,就无法使用接口,该怎么办呢?
# 二:解决方案
解决上述问题的一个合理的解决方案就是简单工厂
简单工厂:提供一个创建对象实例的功能,而无须关心其具体实现。被创建实例的类型可以使接口、抽象类,也可以是具体的类
# 2.1 解决思路
分析上面的问题,虽然不能让模块外部知道模块内的具体实现,但是模块内部是可以知道实现类的,而且创建接口是需要具体实现类的
那么干脆在模块内部新建一个类,在这个类里面来创建接口,然后把创建好的接口返回给客户端,这样外部应用就只需要根据这个类来获取相应的接口对象,然后就可以操作接口定义的方法了。把这样的对象称为简单工厂,就叫Factory吧
这样一来,客户端就可以通过这个Factory来获取需要的接口对象,然后调用接口的方法来实现需要的功能,而且客户端也不用再关心具体实现了
# 2.2 结构和说明
简单工厂的结构如图所示:
- NewApi:定义客户所需要的功能接口
- Impl*:具体实现Api的实现类,可能会有多个
- Factory:工厂,选择合适的实现类来创建Api接口对象
- Client:客户端,通过Factory去获取Api接口对象,然后面向Api接口编程
# 2.3 代码示例
接口:(NewApi.java)
public interface NewApi {
void operation(String s);
}
2
3
实现类:(ImplA.java 和 ImplB.java)
public class ImplA implements NewApi {
@Override
public void operation(String s) {
System.out.println("ImplA s==" + s);
}
}
2
3
4
5
6
public class ImplB implements NewApi{
@Override
public void operation(String s) {
System.out.println("ImplB s==" + s);
}
}
2
3
4
5
6
客户端:(Client2.java)
public class Client2 {
public static void main(String[] args) {
NewApi api = Factory.createApi(1);
api.operation("正在使用简单工厂"); // ImplA s==正在使用简单工厂
}
}
2
3
4
5
6
# 2.4 小结
事实上,简单工厂能帮助我们真正开始面向接口编程,像以前的做法,其实只是用到了接口的多态那部分的功能,最重要的 "封装隔离性" 并没有体现出来
# 三:模式讲解
# 3.1 典型疑问
首先来解决一个常见的疑问:可能有朋友会认为,上面示例中的简单工厂看起来不就是把客户端里面的 "new ApiImpl()" 移动到简单工厂里面吗?不还是一样通过new一个实现类来得到接口吗?把 "new Impl()" 这句话放到客户端和放到简单工厂里面有什么不同吗?
理解这个问题的重点就在于理解简单工厂所处的位置
根据前面的学习,我们知道接口是用来封装隔离具体的实现的,目标就是不要让客户端知道封装体内部的具体实现。简单工厂的位置是位于封装体内的,也就是简单工厂是跟接口和具体的实现在一起的,算是封装体内部的一个类,所以简单工厂知道具体的实现类是没有关系的
# 3.2 命名的建议
类名建议为 "模块名称+Factory",比如:用户模块的工厂就称为:UserFactory
方法名称通常为 "get+接口名称" 或者是 "create+接口名称",比如:有一个接口名称为UserEbi,那么方法名称通常为:getUserEbi 或者是 createUserEbi
当然,也有一些朋友习惯于把方法名称命名为 "new+接口名称",比如:newUserEbi,我们不是很建议。因为new在Java中代表特定的含义,而且通过简单工厂的方法来获取对象实例,并不一定每次都是要new一个新的实例。如果使用newUserEbi,这会给人错觉,好像每次都是new一个新的实例一样
# 3.3 优缺点
帮助封装:简单工厂虽然很简单,但是非常友好的帮助我们实现了组件的封装,然后让组件外部能真正面向接口编程。
解耦:通过简单工厂,实现了客户端和具体实现类的解耦。如同上面的例子,客户端根本就不知道具体是由谁来实现,也不知道具体是如何实现的,客户端只是通过工厂获取它需要的接口对象。
可能增加客户端的复杂度:如果通过客户端的参数来选择具体的实现类,那么就必须让客户端能理解各个参数所代表的具体功能和含义,这会增加客户端使用的难度,也部分暴露了内部实现,这种情况可以选用可配置的方式来实现。
不方便扩展子工厂:私有化简单工厂的构造方法,使用静态方法来创建接口,也就不能通过写简单工厂类的子类来改变创建接口的方法的行为了。不过,通常情况下是不需要为简单工厂创建子类的。
# 3.4 思考简单工厂
1. 简单工厂的本质
简单工厂的本质是:选择实现。
注意简单工厂的重点在选择,实现是已经做好了的。就算实现再简单,也要由具体的实现类来实现,而不是在简单工厂里面来实现。简单工厂的目的在于为客户端来选择相应的实现,从而使得客户端和实现之间解耦,这样一来,具体实现发生了变化,就不用变动客户端了,这个变化会被简单工厂吸收和屏蔽掉。
实现简单工厂的难点就在于 "如何选择" 实现,前面讲到了几种传递参数的方法,那都是静态的参数,还可以实现成为动态的参数。比如:在运行期间,由工厂去读取某个内存的值,或者是去读取数据库中的值,然后根据这个值来选择具体的实现等等。
2. 何时选用简单工厂
建议在如下情况中,选用简单工厂:
如果想要完全封装隔离具体实现,让外部只能通过接口来操作封装体,那么可以选用简单工厂,让客户端通过工厂来获取相应的接口,而无需关心具体实现;
如果想要把对外创建对象的职责集中管理和控制,可以选用简单工厂,一个简单工厂可以创建很多的、不相关的对象,可以把对外创建对象的职责集中到一个简单工厂来,从而实现集中管理和控制。
# 3.5 相关模式
简单工厂是用来选择实现的,可以选择任意接口的实现,一个简单工厂可以有多个用于选择并创建对象的方法,多个方法创建的对象可以有关系也可以没有关系。
简单工厂的本质是选择实现,所以它可以跟其它任何能够具体的创建对象实例的模式配合使用,比如:单例模式、原型模式、生成器模式等等
# 四:Spring中使用
接口:(Pay.java)
/**
* 支付接口
*/
public interface Pay {
/**
* 支付操作方法
*/
void pay();
}
2
3
4
5
6
7
8
9
10
实现类:(WeiXinPay.java 和 ZhiFuBaoPay.java)
@Component("WX")
public class WeiXinPay implements Pay{
@Override
public void pay() {
System.out.println("微信支付");
}
}
2
3
4
5
6
7
@Component("ZFB")
public class ZhiFuBaoPay {
public void pay() {
System.out.println("微信支付");
}
}
2
3
4
5
6
工厂:(PayFactory.java)
/**
* 工厂类,用来创造具体支付业务对象
*/
@Component
public class PayFactory {
/**
* 项目启动后,Spring会为实现了Pay接口并添加注解的类穿件对象实例,并放到此map中
*/
@Autowired
private Map<String, Pay> payMap;
public Pay createPay(String type) {
return payMap.get(type);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
测试:(TestPay.java)
@RunWith(SpringRunner.class)
@SpringBootTest
public class TestPay {
@Autowired
private PayFactory payFactory;
@Test
public void testPay() {
Pay pay = payFactory.createPay("WX");
pay.pay();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14