设计模式 工厂模式

words: 2.6k    views:    time: 11min

工厂模式负责将大量拥有共同接口的类实例化,它可以动态决定将哪一个类实例化

1. 静态工厂模式

静态工厂模式,就是一个工厂类根据传入的参数决定创建出哪一种产品类的实例。

1.1. 结构

  • 工厂类角色(Factory):工厂核心类,含有相关的业务判断逻辑,在客户端的直接调用下创建产品对象,往往由一个具体类实现。
  • 抽象产品角色(Product):工厂创建的对象的父类或它们共同的接口,可以用一个接口或抽象类实现。
  • 具体产品角色(ConcreteProduct):工厂所创建的对象的具体类型。

一个工厂类可以拥有多于一个的工厂方法,分别负责创建不同产品的对象。如果系统只有一个具体产品角色的话,也可以省略掉抽象产品角色,有时工厂角色也可以由抽象产品角色扮演,比如java.text.DataFormat。甚至三个角色可以全部合并,比如在单例模式中,就是产品自身提供的一个静态工厂。

简单工厂模式的核心是工厂类,其中含有必要的判断逻辑,可以决定在什么条件下创建哪一类产品的实例,而客户端则可以免除直接创建产品对象的责任。但每次有新的产品加到系统中时,都必须修改工厂类,并且由于简单工厂模式使用静态工厂方法,无法继承,这就其违背了对扩展开放对修改关闭的原则了。

1.2. 应用

1.2.1. java Calendar
java.util.Calendar
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
public static Calendar getInstance(TimeZone zone, Locale aLocale) {
return createCalendar(zone, aLocale);
}

private static Calendar createCalendar(TimeZone zone, Locale aLocale){

CalendarProvider provider =
LocaleProviderAdapter.getAdapter(CalendarProvider.class, aLocale)
.getCalendarProvider();
if (provider != null) {
try {
return provider.getInstance(zone, aLocale);
} catch (IllegalArgumentException iae) {
// fall back to the default instantiation
}
}

//上面部分跟下去最后到CalendarProvider的实现CalendarProviderImpl,
//其最后getInstance的实现委托给了Calendar中的Build.build(),与下面类似也就是静态工厂模式
Calendar cal = null;
if (aLocale.hasExtensions()) {
String caltype = aLocale.getUnicodeLocaleType("ca");
if (caltype != null) {
switch (caltype) {
case "buddhist":
cal = new BuddhistCalendar(zone, aLocale);
break;
case "japanese":
cal = new JapaneseImperialCalendar(zone, aLocale);
break;
case "gregory":
cal = new GregorianCalendar(zone, aLocale);
break;
}
}
}

if (cal == null) {
// If no known calendar type is explicitly specified,
// perform the traditional way to create a Calendar:
// create a BuddhistCalendar for th_TH locale,
// a JapaneseImperialCalendar for ja_JP_JP locale, or
// a GregorianCalendar for any other locales.
// NOTE: The language, country and variant strings are interned.
//如果是泰国地区就返回佛历,日本地区则返回日本帝国历
//其他国家和地区就返回格里高利历,即公历,没有阴历
if (aLocale.getLanguage() == "th" && aLocale.getCountry() == "TH") {

cal = new BuddhistCalendar(zone, aLocale);
} else if (aLocale.getVariant() == "JP" && aLocale.getLanguage() == "ja"
&& aLocale.getCountry() == "JP") {
cal = new JapaneseImperialCalendar(zone, aLocale);
} else {
cal = new GregorianCalendar(zone, aLocale);
}
}
return cal;
}

DateFormat也是类似的,本身是一个抽象类,但却提供了很多的静态工厂方法。虽然抽象类不能创建实例,但其工厂方法可以返回子类的实例,并声明为抽象类型。而这正是多态性原则运用。这样利用超类将具体类型隐藏起来(里氏替换)的好处是如果将来有新的子类型加到系统中,那么工厂类可以直接替换返回的子类实例,并且对调用方是透明的

1.2.2. mysql Driver
com.mysql.cj.jdbc.NonRegisteringDriver
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
@Override
public java.sql.Connection connect(String url, Properties info) throws SQLException {

try {
if (!ConnectionUrl.acceptsUrl(url)) {
/*
* According to JDBC spec:
* The driver should return "null" if it realizes it is the wrong kind of driver to connect to the given URL. This will be common, as when the
* JDBC driver manager is asked to connect to a given URL it passes the URL to each loaded driver in turn.
*/
return null;
}

ConnectionUrl conStr = ConnectionUrl.getConnectionUrlInstance(url, info);
switch (conStr.getType()) {
case SINGLE_CONNECTION:
return com.mysql.cj.jdbc.ConnectionImpl.getInstance(conStr.getMainHost());

case LOADBALANCE_CONNECTION:
return LoadBalancedConnectionProxy.createProxyInstance((LoadbalanceConnectionUrl) conStr);

case FAILOVER_CONNECTION:
return FailoverConnectionProxy.createProxyInstance(conStr);

case REPLICATION_CONNECTION:
return ReplicationConnectionProxy.createProxyInstance((ReplicationConnectionUrl) conStr);

default:
return null;
}

} catch (UnsupportedConnectionStringException e) {
// when Connector/J can't handle this connection string the Driver must return null
return null;

} catch (CJException ex) {
throw ExceptionFactory.createException(UnableToConnectException.class,
Messages.getString("NonRegisteringDriver.17", new Object[] { ex.toString() }), ex);
}
}

2. 工厂模式

工厂模式中,工厂类不再负责所有的产品创建,而是将具体的产品创建工作交给子类去完成,自己抽象出子类工厂必须实现的接口,这种进一步抽象的结果,使工厂模式允许系统在不修改具体工厂角色的情况下,引进新的产品

2.1. 结构

  • 抽象工厂角色(Factory):工厂核心类,给出子类工厂必须实现的工厂方法。
  • 具体工厂角色(ConcreteFactory):实现工厂方法接口,含有业务相关的逻辑,创建对应的产品对象。
  • 抽象产品角色(Product):工厂所创建的对象的超类。
  • 具体产品角色(ConcreteProduct):具体产品类型

工厂模式的核心的是抽象工厂类,它允许多个具体工厂类将创建行为继承下来,从而可以成为多个静态工厂模式的综合。如果系统需要加入一个新的产品,那么只需要向系统中加入这个产品类及其所对应的工厂类,而不需要修改已经存在的工厂,因此它可以比静态工厂模式更好的支持开闭原则。

2.2. 应用

2.2.1. java Collection
java.lang.Collection
1
2
3
4
5
6
public interface Collection<E> extends Iterable<E> {

Iterator<E> iterator();

//...
}

Collection要求所有子类集合都实现工厂方法iterator(),返回一个Iterator实例,这样统一了各种集合类的迭代方式。

这里可以看到Collection还继承了Iterable,而且Iterable只定义了一个接口iterator(),那么Collection中的完全可以去掉iterator()的声明,它这样做也只是为了方便。从注释中可以看到,Collection@since 1.2,而Iterable@since 1.5,应该是在扩展时发现需要迭代操作的不仅仅只有集合类,而其他类又不想实现Collection其他的操作,因此最好的做法就是再抽象出一层Iterable,这样也比较符合语义。

2.2.2. java URL

创建一个URL对象很简单URL url = new URL("http://www.baidu.com");,只要将一个合法的URL就行。不过它要在初始化时会要识别出url中的协议protocol,然后根据协议获取或者创建对应的流处理器URLStreamHandler

java.net.URL
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
static Hashtable<String,URLStreamHandler> handlers = new Hashtable<>();

private static Object streamHandlerLock = new Object();

/**
* 首先根据protocol从handlers中获取,如有存在则直接返回
* 否则新建handler,然后在放入handlers时进行同步检查,
* 如果其他线程已经创建并放入了handlers,那么直接用已经放入的,而丢弃新建的
*/
static URLStreamHandler getURLStreamHandler(String protocol) {

URLStreamHandler handler = handlers.get(protocol);
if (handler == null) {

boolean checkedWithFactory = false;

// Use the factory (if any)
if (factory != null) {
handler = factory.createURLStreamHandler(protocol);
checkedWithFactory = true;
}

// Try java protocol handler
if (handler == null) {
String packagePrefixList = null;

packagePrefixList = java.security.AccessController.doPrivileged(
new sun.security.action.GetPropertyAction(protocolPathProp,""));
if (packagePrefixList != "") {
packagePrefixList += "|";
}

// REMIND: decide whether to allow the "null" class prefix
// or not.
packagePrefixList += "sun.net.www.protocol";

StringTokenizer packagePrefixIter =
new StringTokenizer(packagePrefixList, "|");

while (handler == null &&
packagePrefixIter.hasMoreTokens()) {

String packagePrefix =
packagePrefixIter.nextToken().trim();
try {
String clsName = packagePrefix + "." + protocol +
".Handler";
Class<?> cls = null;
try {
cls = Class.forName(clsName);
} catch (ClassNotFoundException e) {
ClassLoader cl = ClassLoader.getSystemClassLoader();
if (cl != null) {
cls = cl.loadClass(clsName);
}
}
if (cls != null) {
handler =
(URLStreamHandler)cls.newInstance();
}
} catch (Exception e) {
// any number of exceptions can get thrown here
}
}
}

synchronized (streamHandlerLock) {

URLStreamHandler handler2 = null;

// Check again with hashtable just in case another
// thread created a handler since we last checked
handler2 = handlers.get(protocol);

if (handler2 != null) {
return handler2;
}

// Check with factory if another thread set a
// factory since our last check
if (!checkedWithFactory && factory != null) {
handler2 = factory.createURLStreamHandler(protocol);
}

if (handler2 != null) {
// The handler from the factory must be given more
// importance. Discard the default handler that
// this thread created.
handler = handler2;
}

// Insert this handler into the hashtable
if (handler != null) {
handlers.put(protocol, handler);
}

}
}
return handler;
}

而这个流处理器URLStreamHandler就是一个工厂,相当于抽象工厂角色,URLConnection openConnection(URL u)就是它给出的工厂方法,URLConnection则是它对应的产品。

java.net.URLStreamHandler
1
2
3
4
5
6
public abstract class URLStreamHandler {

abstract protected URLConnection openConnection(URL u) throws IOException;

//...
}

看下它的实现,即具体工厂,从中就可以看到很熟悉的http和https了,

从上面的说明可以知道,URLConnection只是一个抽象产品,具体的产品应该是它的实现,由具体工厂根据给定的协议进行创建。

3. 抽象工厂模式

3.1. 结构

假设现在有一些产品,它们属于一个以上的等级结构,不过这些结构是相同的,我们可以将一个等级结构中的产品称为一族。举个例子:有时可能某种功能需要很多种产品来组成,每种产品只代表功能的一部分,而现在有多个这样的功能,而且这些功能需要的产品类组成是一致的。

可以使用工厂模式,针对每种产品设计一个独立的工厂,然后针对每个功能实现一个具体的工厂。但这样对客户端就比较麻烦,如果要获取一个产品族中的各个产品,首先还得先知道每个产品对应的工厂。因此,可以对这些工厂进行一个合并,按照产品族进行划分,这样客户端只要知道在产品哪个产品族就行了。

比如下图中,productA1productB1就是一族产品,对应的工厂为creator1productA2productB2是另一族产品,对应的工厂为creator2。如果要添加新的产品族,只需新增一个creator的实现即可。不过如果某个产品族的结构要发生修改则会比较麻烦,因为如果修改抽象工厂,就会影响其他产品族,而如果直接修改具体工厂将会对客户端不可见,因为客户端面向的是抽象工厂。

因此,抽象工厂模式的结构与工厂模式是一样的,它就是对工厂模式的一个合并。

3.2. 应用

3.2.1. java Connection

Connection就相当于一个产品族,每种数据库都有自己的一套具体产品,而它们的组成结构是一致的。

java.sql.Connection
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public interface Connection  extends Wrapper, AutoCloseable {

Statement createStatement() throws SQLException;

PreparedStatement prepareStatement(String sql) throws SQLException;

CallableStatement prepareCall(String sql) throws SQLException;

DatabaseMetaData getMetaData() throws SQLException;

Savepoint setSavepoint() throws SQLException;

Clob createClob() throws SQLException;

Blob createBlob() throws SQLException;

SQLXML createSQLXML() throws SQLException;

// ...
}


参考:

  1. 《java与模式》
  2. 《Java设计模式》