设计模式:简单工厂

此篇博客参考书籍《研磨设计模式》,书的封面太丑了,用言叶之庭当封面吧,相比GoF的设计模式,此书更加通俗易懂,适合作为设计模式的入门书籍。

场景问题

在Java应用开发中,我们应该遵守“面向接口编程”的原则,而接口的思想浓缩成一句话就是“封装隔离”,此处的封装指的是“对被隔离体的行为的封装”,而隔离指的是外部的调用和内部实现,外部的调用只能通过接口来进行调用,而不知道内部的具体实现。(书上的原话,基本是照抄的,吼吼吼)

在刚学JavaSE的时候,有时会遇到如下类型的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 定义一个接口
public interface Api{
public void test(String s);
}

// 定义一个实现类
public class Impl implements Api{
public void test(String s){
System.out.println("let's paly ♂ a ♂ game:"+s);
}
}

// 客户端:测试使用Api接口
public class Client{
public static void main(String[] args){
Api api = new Impl();
api.test("哲学♂ 冲浪板!!!");
}
}

在客户端中有这样一句代码

1
Api api = new Impl()

你会发现客户端在调用时,它不但知道了接口,同时还知道了具体的实现是Impl,然而接口的思想是隔离封装,客户端不应该知道实现类是Impl,但是如果将new Impl()去掉,Api的接口对象又无法获得。

如果将这个问题描述一下就是:在Java编程中,需要只知接口不知实现的情况,该怎么办?(为什么有这样的需要/滑稽)

解决方案

定义

用来解决上述问题的一个合理解决方案就是简单工厂,关于简单工厂的定义是这样的:提供一个创建对象实例的功能,而无需关心其具体实现。被创建实例的类型可以是接口,抽象类,也可以是具体的类

解决问题的思路

虽然不能让模块外部知道模块内部的具体实现,但是模块内部是可以知道的,那么干脆在模块内部新建一个类,在这个类里面来创建接口,然后把创建好的接口返回给客户端。

使用简单工厂重写示例

下面我们用简单工厂的模式重写实现一下刚才的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 定义一个接口
public interface Api{
public void test(String s);
}

// 定义一个实现类
public class Impl implements Api{
public void test(String s){
System.out.println("let's paly ♂ a ♂ game:"+s);
}
}

// 定义一个工厂
public class Factory(){
public static Api creatApi(){
return new Impl();
}
}

// 客户端:测试使用Api接口
public class Client{
public static void main(String[] args){
Api api = Factory.createApi();
api.test("哲学♂ 冲浪板!!!");
}
}

模式讲解

认识简单工厂设计模式

简单工厂的功能就是创建对象,我们在使用简单工厂的时候,通常不用创建简单工厂类的实例,而是将创建对象的方法变为静态方法,所以简单工厂又可以称为静态工厂。如果想要防止客户端无谓的创造简单工厂实例。我们还可以将构造方法私有化。

虽然从理论上讲,简单工厂可以创建任何对象,但我们再使用时,尽量将创建的范围独立成一个组建或者模块级别,否则这个工厂类会显得职责不明,有点大杂烩的感觉。

简单工厂的优点和不足

优点

​ 帮助封装,去除了客户端与具体实现类的依赖,封装了对象的创建过程。

不足

​ 工厂类中包含了多个实例创建的逻辑,一旦工厂出现问题,会影响到所有实例的创建。

​ 如果想要添加一个新的实例让简单工厂来创建,就必须去修改简单工厂的创建方法,不太符合”开放-关闭”(对扩展开放,对修改关闭)原则。

增强扩展性

利用反射技术可以增强简单工厂的扩展性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 定义一个接口
public interface Api{
public void test(String s);
}

// 定义一个实现类
public class Impl implements Api{
public void test(String s){
System.out.println("let's paly ♂ a ♂ game:"+s);
}
}

// 使用反射技术实现创建对象的方法
public class Factory(){
public static Api creatApi(Class c){
Api api = null;
try {
api = (Api) Class.forName(c.getName()).newInstance();
} catch (InstantiationException e) {
System.out.println("不支持抽象类或接口");
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
System.out.println("没有足够权限,即不能访问私有对象");
} catch (ClassNotFoundException e) {
System.out.println("类不存在");
e.printStackTrace();
}

return api;
}
}

// 测试调用
public class Client{
public static void main(String[] args){
Api api = Factory.createApi(Impl.class);
api.test("哲学♂ 冲浪板!!!");
}
}

简单工厂在JDK中的应用

在JDK中最典型的简单工厂的应用就是JDBC

将关系型数据库认为是抽象产品,将MySQL,Oracle认为是具体产品,

DriverManager是工厂类

应用程序通过JDBC接口使用关系型数据库,不需关心使用哪种数据库

直接使用DriverManager的静态方法去得到该数据库的Connection即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.brave.simplefactory.jdk;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

/**
* 简单工厂在JDK中的使用
*
*
*/
public class JDBC {

public static void main(String[] args) {

Connection conn = null;
try {
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1/student");
PreparedStatement ps = conn.prepareStatement("select * from mysql.test");
ps.execute();
} catch (SQLException ex) {
System.out.println("Execute query failed " + ex);
} catch (ClassNotFoundException e) {
System.out.println("Load mysql driver failed " + e);
} finally {
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
}
}
}

}
}