Java反射机制与工厂模式
工厂模式
简介
工厂模式是最常用的实例化对象模式。
工厂模式的主要作用就是使用工厂方法代替new操作。
为什么要使用工厂模式?直接new不好吗?
直接new没有什么不好,只是工厂模式可以给系统带来更好的可扩展性和尽量少的修改量。
分类
工厂模式一般有两类,一类是工厂方法模式,另一类是抽象工厂模式。但是《head first 设计模式》中,通过某些例子,实际上把工厂模式分为三种:
- 简单工厂模式(Simple Factory)
- 工厂方法模式(Factory Method)
- 抽象工厂模式(Abstract Factory)
其中简单工厂模式是工厂方法模式的一种特例。
使用情况
- 在编码时不能预见需要创建哪种类的实例
- 系统不应依赖于产品类实例如何被创建,组合和表达的细节。
工厂方法模式组成
- 抽象工厂角色:这是工厂方法模式的核心,它与应用程序无关。是具体工厂角色必须实现的接口或者必须继承的父类。在java中它由抽象类或者接口来实现。
- 具体工厂角色:它含有和具体业务逻辑有关的代码。由应用程序调用以创建对应的具体产品的对象。在java中它由具体的类来实现。
- 抽象产品角色:它是具体产品继承的父类或者是实现的接口。在java中一般有抽象类或者接口来实现。
- 具体产品角色:具体工厂角色所创建的对象就是此角色的实例。在java中由具体的类来实现。
- 客户端:调用工厂类去产生实例,并调用这些实例的方法进行相应的工作。
简单工厂模式实际上是一种静态的工厂方法模式。简单工厂模式由一个工厂类根据传入的参数决定创建哪一种的产品类。
现在先给出一个没有使用反射机制的工厂模式。
当你在玩最近流行的游戏,英雄联盟LOL或者是DOTA/DOTA2,你和队友包括敌方阵营都会选择英雄。这个英雄的选择过程我们可以看做是一个new一个英雄。例如我要选择(new)一个暗夜刺客,然后我就得到了一个暗夜刺客的实例,接着通过这个实例进行游戏。下面的例子,用简单工厂模式来生产英雄模拟以上过程。
简单工厂模式DEMO:
首先,完成一个英雄的接口,这对应了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读文件和打印输出上。
抛出这些技术细节问题,
总之,反射机制给工厂模式带来了新的体验。