前言

  工厂方法(Factory Method)模式的定义:定义一个创建产品对象的工厂接口,将实际创建工作推迟到子类当中。核心工厂类不再负责产品的创建,这样核心类成为一个抽象工厂角色,仅负责具体工厂子类必须实现的接口,这样进一步抽象化的好处是使得工厂方法模式可以使系统在不修改具体工厂角色的情况下引进新的产品。
  可以看到工厂方法模式中定义了一个工厂接口,而具体的创建工作推迟到具体的工厂类,它是对简单工厂模式中的工厂类进一步抽象化,从而产生一个工厂类的抽象和实现体系,从而弥补简单工厂模式对修改开放的诟病。
  下面博主给出工厂方法模式的类图。


栗子

  可以看到,上面右半部分是产品抽象和实现体系,左半部分是工厂抽象和实现体系,其中工厂体系依赖于产品体系,每一个工厂负责创造一种产品,这就省去了简单工厂中的else if判断,又客户端决定实例化一个特定的工厂去创建相应的产品。
  下面博主简单的使用JAVA代码诠释上述标准的工厂方法模式的类图。
  1. 首先是抽象产品接口
1
2
3
4
5
6
7
public interface Light {

public void turnOn();

public void turnOff();

}
  2. 下面是具体的产品
1
2
3
4
5
6
7
8
9
10
11
public class BuldLight implements Light{

public void turnOn() {
System.out.println("BuldLight On");
}

public void turnOff() {
System.out.println("BuldLight Off");
}

}
1
2
3
4
5
6
7
8
9
10
11
public class TubeLight implements Light{

public void turnOn() {
System.out.println("TubeLight On");
}

public void turnOff() {
System.out.println("TubeLight Off");
}

}
  3. 下面是抽象的工厂接口
1
2
3
4
5
public interface Creator {

public Light createLight();

}
  4. 下面是创建指定产品的具体工厂
1
2
3
4
5
6
7
public class BuldCreator implements Creator{

public Light createLight() {
return new BuldLight();
}

}
1
2
3
4
5
6
7
public class TubeCreator implements Creator{

public Light createLight() {
return new TubeLight();
}

}
  5. 下面我们写个测试类去实验一下这个工厂方法模式的实例代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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();
}
}

运行结果如下:

1
2
3
4
BuldLight On
BuldLight Off
TubeLight On
TubeLight Off
  可以看到,我们使用可以随意的在具体的工厂和产品之间切换,并且不需要修改任何代码,就可以让原来的程序正常运行,这也是工厂方法模式对扩展开放的表现,另外工厂方法模式弥补了简单工厂模式不满足开闭原则的诟病,当我们需要增加产品时,只需要增加相应的产品和工厂类,而不需要修改现有的代码。
  上面的示例可以比较清楚的展示各个类之间的关系,但是始终缺乏说服力,因为它完全没有什么实际意义,下面博主就给出一些我们接触过的例子来说明工厂方法模式的好处。
  关于能够说明工厂方法模式的实例,博主翻遍了所有能找到的源码,想寻找一个让各位读者既能学习到新的东西,又能对工厂方法理解更深的现有的优秀框架的设计。经过跋山涉水,博主决定还是拿数据库连接来说事,我知道你想说,我去,又是数据库连接。博主只想说,我们每天做的最多的就是增删改查好吗,其它的咱也不认识啊,囧。
  众所周知,为了统一各个数据库操作的标准,于是有了JDBC的API,它用于给我们这种被称作只会使用现成的东西的程序猿,提供一系列统一的,标准化的操作数据库的接口。其实JDBC的各个类或接口,就是我们操作数据库的过程中各个协助者的抽象,这样的设计是为了让我们对数据库的操作依赖于抽象,还记得我们在设计模式总纲中提到的一句话吗,用抽象构建框架,用细节扩展实现。
  JDBC API(即抽象的接口或类)就是整个数据库操作的框架,而各个数据库的驱动就是那些细节。而我们的操作依赖于JDBC API,而不是任何一个具体数据库的细节。
  JDBC是如何统一了数据库世界的呢?其实最主要的就是靠两个接口,就统一了世界。
  来看第一个接口Driver,附上源码。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package java.sql;

import java.sql.DriverPropertyInfo;
import java.sql.SQLException;

/**
* The interface that every driver class must implement.
*/
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();
}
  由于篇幅,博主删掉了很多注释,只保留了这个类注释的第一句话,翻译过来是这是一个任何驱动类都必须实现的接口。多么霸气啊。也就是每个数据库厂商都必须实现这个接口来提供JDBC服务,即java数据库连接服务,来方便程序猿对数据库应用编程。
  我们先忽略掉下面的五个方法,第一个方法毫无疑问是这个接口中相对而讲最重要的方法了,即创造一个数据库连接,虽然方法名称是connect,但是我觉得这个方法完全可以改为createConnection。
  提到Connction,这个接口我们一定不陌生,它的源码也已经在代理模式一章出现过,这里我们再次让它出场,我依旧会删掉它的大部分方法,限于篇幅。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package java.sql;

import java.sql.PreparedStatement;
import java.sql.SQLException;

/**
* <P>A connection (session) with a specific
* database. SQL statements are executed and results are returned
* within the context of a connection.
* <P>
*/
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的封装,或许我们不能经常看到,但博主相信它活在每个程序猿的心中。
  DriverMananger在这个设计当中扮演者一个管理者的角色,它帮我们管理数据库驱动,让我们不需要直接接触驱动接口,我们获取连接只需要和DriverManager打交道就可以,也就是说客户端依赖于DriverManager和Connection就可以完成工作,不再需要与Driver关联,所以上述说我们依赖于Driver和Connection,现在DriverManager帮我们管理Driver,那我们只需要依赖于DriverManager和Connection就可以了。
  博主在类图中拉出了DriverManager的方法,其中的registerDriver方法正是我们注册数据库驱动的入口。来看看mysql的Driver中做了什么,oracle类似。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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,用户名和密码。
  原来我们都是活在温室里的花朵,都被这些设计者细心呵护着,生怕我们知道一点底层的东西。记得博主当初第一次看到Class.forName时,还觉得真是个神奇的东西,没想到只是这些设计者给我们的糖外衣。
  工厂方法模式的好处和适用的场景都相对比较好理解。
  好处就是,从类关系上来说,它可以让客户端与具体的工厂与产品解耦,从业务角度来说,它让客户端与具体的产品解耦。
  适用的场景就是我们需要一个产品帮我们完成一项任务,但是这个产品有可能有很多品牌(像这里的mysql,oracle),为了保持我们对产品操作的一致性,我们就可能要用到工厂方法模式。
  工厂方法模式也有它所不足的地方,可能你会说,这多好啊,我们操纵数据库不再需要关心具体是哪个数据库。是的,你很爽啊,那是因为这些产品的实现都不用你写啊,都是数据库厂商给你写的。
  假设产品数量巨多,而且需要我们亲手去逐个实现的时候,工厂方法模式就会增加系统的复杂性,到处都是工厂类和产品类,而且这里所说的工厂类和产品类只是概念上的,真正的产品可能不是一两个类就能搞定,否则mysql和oracle的驱动包为啥要那么多类,而不是就一个Driver和一个Connection。
  当然这也不是绝对,比如我们经常使用的HashSet和ArrayList,也是使用的工厂方法模式,各位看下他们的类图就看出来了。


  各位可能会说,不对啊,这和我们刚才理解的不太一样啊,按照刚才的说法,我们不是应该直接使用iterable和iterator吗?这样多牛X,我们不依赖于具体产品了。对于这个博主表示三条黑线垂下,sun或者说oracle为了集合框架给你提供了这么多具备各个特性的集合,你只用iterator和iterable,估计当初参与设计集合框架的人都要气的去shi了。。
  上述这便是工厂方法模式另外一种用法了,刚才因为我们不关心真正的产品是什么,所以我们直接使用抽象接口操作。但是我们使用iterable和iterator的时候,我们是关心真正产品的特性的,所以为了使用产品的特性,我们就需要使用产品特有的接口了,比如特殊的SortedSet可排序,比如ArrayList可以有重复元素,可以根据索引获取元素等等。当然你依然是可以使用iterable和iterator的,但是不管你用什么,在这种场景下,产品是你自己选的,一句话,你随便。。。
  两种使用方式一种是对使用者透明的,一种是不透明的,一种是使用者对具体的产品不关心,这种情况下,一般产品提供的功能是类似的。一种是使用者非常了解产品的特性,并想使用产品的特性,这种情况下,一般产品只提供最基本的一致的功能,但每个产品都会有自己独特的一面。
  但是博主个人觉得真正做项目的过程当中很少用到工厂方法模式,这个模式更多的是帮助我们理解现有的开源项目,就像现在,你是不是对JDBC的大体框架有了一定认识了呢,如果你不知道这个模式,可能看源码会觉得一头雾水呢。
  好了,工厂方法模式就给各位分享到这吧,感谢各位的欣赏❤️

ps:因作者能力有限,有错误的地方请见谅

  • 喜欢这篇文章的话可以用快捷键 Ctrl + D 来收藏本页

最后更新: 2018年11月15日 11:10

原始链接: https://blog.hdqyf.club/2018/11/15/20181115-工厂方法模式详解/

× 请我吃糖~
打赏二维码