工厂模式是最常用的实例化对象模式。
工厂模式的主要作用就是使用工厂方法代替new操作。
为什么要使用工厂模式?直接new不好吗?
直接new没有什么不好,只是工厂模式可以给系统带来更好的可扩展性和尽量少的修改量。
工厂模式一般有两类,一类是工厂方法模式,另一类是抽象工厂模式。但是《head first 设计模式》中,通过某些例子,实际上把工厂模式分为三种:
其中简单工厂模式是工厂方法模式的一种特例。
简单工厂模式实际上是一种静态的工厂方法模式。简单工厂模式由一个工厂类根据传入的参数决定创建哪一种的产品类。
现在先给出一个没有使用反射机制的工厂模式。
当你在玩最近流行的游戏,英雄联盟LOL或者是DOTA/DOTA2,你和队友包括敌方阵营都会选择英雄。这个英雄的选择过程我们可以看做是一个new一个英雄。例如我要选择(new)一个暗夜刺客,然后我就得到了一个暗夜刺客的实例,接着通过这个实例进行游戏。下面的例子,用简单工厂模式来生产英雄模拟以上过程。
首先,完成一个英雄的接口,这对应了product接口,即抽象的产品类。
1
2
3
4
5
|
package
com.aaron.reflect.factory;
public
interface
Hero {
public
void
say();
}
|
然后用几个不同的类去实现这个接口,这些类就是实际上产品的生产者。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
package
com.aaron.reflect.factory;
public
class
EarthShaker
implements
Hero {
@Override
public
void
say() {
System.out.println(
"撼地神牛:Do not push me or I will impale you on my horns."
);
}
}
//------------------------分割线------------------------
package
com.aaron.reflect.factory;
public
class
NeverMore
implements
Hero {
@Override
public
void
say() {
System.out.println(
"影魔:What, mortal?"
);
}
}
|
然后,我们实现一个静态的工厂方法,在这个工厂类中,静态地得到产品的实例
1
2
3
4
5
6
7
8
9
10
11
12
13
|
package
com.aaron.reflect.factory;
public
class
HeroesFactory {
public
static
Hero choose(String shortName){
Hero hero =
null
;
if
(shortName.equals(
"ES"
)){
hero =
new
EarthShaker();
}
else
if
(shortName.equals(
"SF"
)) {
hero =
new
NeverMore();
}
return
hero;
}
}
|
测试一下:
1
2
3
4
5
6
7
8
|
package
com.aaron.reflect.factory;
public
class
FactoryTest {
public
static
void
main(String[] args) {
HeroesFactory.choose(
"ES"
).say();
HeroesFactory.choose(
"SF"
).say();
}
}
|
运行结果:
1
2
|
撼地神牛:Do not push me or I will impale you on my horns.
影魔:What, mortal?
|
OK,一个简单完整的简单工厂模式就实现了。
工厂方法模式只是把这里的工厂类再次抽象,抽象出一个工厂接口,当使用ES的时候就新建一个ESFactory的实现,使用SF时,就新建一个SFFactory。这样看似麻烦,但是当业务复杂时却能保证频繁的变更不会导致系统越对越乱,只需要添加一个产品,添加一个用来生产产品的工厂。
然而,这种更改仍然要耗费很多精力,除了接口其余的我们都更改了。
如上面的简单工厂模式中,如果我们要新增一个英雄,屠夫,Pudge。我们需要做如下更改:
新增Pudge类
修改Factory类,增加Pudge的匹配
新增Pudge代码:
1
2
3
4
5
6
7
8
|
package
com.aaron.reflect.factory;
public
class
Pudge
implements
Hero {
@Override
public
void
say() {
System.out.println(
"屠夫:Ah! Fresh meat!"
);
}
}
|
增加Pudge匹配的代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
package
com.aaron.reflect.factory;
public
class
HeroesFactory {
public
static
Hero choose(String shortName){
Hero hero =
null
;
if
(shortName.equals(
"ES"
)){
hero =
new
EarthShaker();
}
else
if
(shortName.equals(
"SF"
)) {
hero =
new
NeverMore();
}
else
if
(shortName.equals(
"TF"
)) {
hero =
new
Pudge();
}
return
hero;
}
}
|
此时,如果我们把一百多个英雄都添加进去,我们就会修改一百多次工厂类。
现在我们可以使用反射机制,来避免这种麻烦。
使用反射机制改写工厂类:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
package
com.aaron.reflect.factory;
public
class
HeroesFactory {
public
static
Hero choose(String shortName){
Hero hero =
null
;
try
{
hero = (Hero) Class.forName(shortName).newInstance();
}
catch
(Exception e) {
e.printStackTrace();
}
return
hero;
}
}
|
代码写到这里,问题就来了。
Class.forName(String str)这个方法的参数必须是包含报名的,例如我想得到一个ES撼地神牛,我仅仅传入“ES”必然不行,传入“EarthShaker”这个类名也找不到对应的类,只有传入完整的包名和类名,"com.aaron.reflect.factory.HeroesFactory"。
怎么来解决呢?其中一个办法是引入properties配置文件。
先看一下properties文件;
1
2
3
|
ES=EarthShaker
SF=NeverMore
TF=Pudge
|
修改工厂类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
package
com.aaron.reflect.factory;
import
java.io.FileInputStream;
import
java.util.Properties;
public
class
HeroesFactory {
public
static
Hero choose(String shortName) {
Hero hero =
null
;
// 从properties文件中读取shortName对应的完整包名
Properties properties =
new
Properties();
try
{
properties.load(
new
FileInputStream(
"src/nameMapping.properties"
));
String fullName = properties.getProperty(shortName);
hero = (Hero) Class.forName(fullName).newInstance();
}
catch
(Exception e) {
e.printStackTrace();
}
return
hero;
}
}
|
这么做就一劳永逸了。
当新增一个产品类时,工厂类就不需要做任何改动了。
但是,程序写到这,应该想到一个很严重的问题,如果工厂类被频繁调用时,没新建一个产品,就要读一次propert ies文件,尽管java对properties的支持非常便捷,但是这么频繁地去操作IO明显在性能上有很大的弱点。
在测试类中,加入计时,得到运行结果如下:
1
2
3
4
|
撼地神牛:Do not push me or I will impale you on my horns.
影魔:What, mortal?
屠夫:Ah! Fresh meat!
耗时:6ms
|
平均耗时5毫秒左右。
这样的话,我们对程序做以下改动。
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
|
package
com.aaron.reflect.factory;
import
java.io.FileInputStream;
import
java.io.IOException;
import
java.util.Properties;
public
class
HeroesFactory {
public
static
Properties init() {
// 从properties文件中读取shortName对应的完整包名
Properties properties =
new
Properties();
try
{
properties.load(
new
FileInputStream(
"src/nameMapping.properties"
));
}
catch
(IOException e) {
e.printStackTrace();
}
return
properties;
}
public
static
Hero choose(String shortName) {
Hero hero =
null
;
try
{
String fullName = HeroesFactory.init().getProperty(shortName);
hero = (Hero) Class.forName(fullName).newInstance();
}
catch
(Exception e) {
e.printStackTrace();
}
return
hero;
}
}
|
然后看一下运行结果:
1
2
3
4
|
撼地神牛:Do not push me or I will impale you on my horns.
影魔:What, mortal?
屠夫:Ah! Fresh meat!
耗时:3ms
|
平均3ms左右。当然这跟机器的性能也有些关系。当然,大部分的耗时应该还是花在了I/O读文件和打印输出上。
抛出这些技术细节问题,
总之,反射机制给工厂模式带来了新的体验。