Java中的反射和工厂模式

什么是反射

  反射之中包含了一个“反”的概念,所以要想解释反射就必须先从“正”开始解释,一般而言,当用户使用一个类的时候,应该先知道这个类,而后通过这个类产生实例化对象,但是“反”指的是通过对象找到类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Test {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
//正的方式创建对象
Person person=new Person();

//反的方式,使用Class的forName静态方法
Class<?> clazz1=Class.forName("com.lgq.Person");
Object object1=clazz1.newInstance();
Person person1=(Person)object1;

//反的方式,使用object的getClass方法
203.
Class<?> clazz2=person.getClass();
Object object2=clazz2.newInstance();
Person person2=(Person)object2;

//反的方式,使用Class的class方法
Class<?> clazz3=Person.class;
Object object3=clazz3.newInstance();
Person person3=(Person)object3;

}
}

反射的作用

  那么现在可以发现,对于对象的实例化操作,除了使用关键字new之外又多了一个反射机制操作,而且这个操作要比之前使用的new复杂一些,可是有什么用?

  对于程序的开发模式之前一直强调:尽量减少耦合,而减少耦合的最好做法是使用接口,但是就算使用了接口也逃不出关键字new,所以实际上new是造成耦合的关键元凶。

简单讲一下工厂模式

简单工厂

  首先我们先来回顾一下以前我们使用过的简单工厂模式

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
public interface Fruit{
void eat();
}

public class Apple implements Fruit {
@Override
public void eat() {
System.out.println("吃Apple");
}
}

public class Orange implements Fruit {
@Override
public void eat() {
System.out.println("吃Orange");
}
}

public class FruitFactory{
public static Fruit createFruit(String fruitType){
if ("Apple".equals(fruitType)){
return new Apple();
}
else if ("Orange".equals(fruitType)){
return new Orange();
}
else{
return null;
}
}
}

public class Client {
public static void main(String[] args) {
Fruit fruit=FruitFactory.createFruit("Apple");
fruit.eat();
}
}

  这里简单说一下工厂模式,以前我们new东西都是自己new,就像现实生活中,很久以前没有制作商品的工厂,那我们获取东西就要自己制造。例子:比如刀耕火种的时代,我们需要需要一把斧子,我们没有工厂,我们只能自己造,只能自己new出来。但是到了,封建时期直至现在,有很多作坊或者工厂,当我们需要某种东西的时候,我们只需要告诉工厂我们需要什么,工厂便会给我们造什么,其实这就相当于我们制造东西的权利交给了工厂,我们变成了客户,我们客户只关心产品之间地差异(产品生产出来的结果,我们获取到了什么产品),而不是生产产品的过程,这个东西由工厂来负责,我们不关心了。

工厂方法模式

  这里我们再次思考一下,上面的代码我们不是一个制造水果的工厂吗?这时候作为客户的我们又想吃Pear了,那么我们的工厂又得重新编辑它的代码,这样就会非常麻烦(因为耦合),那么我们该怎么办呢?

  这里我又想提到工厂模式了,工厂模式里面有个工厂方法,它是用来解决这个简单工厂模式不可拓展性的方法。比如,这时候我们把水果工厂给抽象出来,然后我们去具体实现这个水果工厂,比如说我们实现一个AppleFactory,一个OrangeFactory。这两个工厂作为我们默认的,一开始就有的,到后来我们需要添加水果的产品种类了,我们怎么办呢?比如我们添加一个Pear类,我们使Pear实现Fruit接口,然后创建一个PearFactory实现FruitFactory,然后客户端当创建一个工厂时就创建一个PearFactory,具体看代码

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
public interface Fruit{
void eat();
}
public class Apple implements Fruit {
@Override
public void eat() {
System.out.println("吃Apple");
}
}
public class Orange implements Fruit {
@Override
public void eat() {
System.out.println("吃Orange");
}
}

//这里创建一个Pear实现Fruit接口
public class Pear implements Fruit {
@Override
public void eat() {
System.out.println("吃Pear");
}
}

//使FruitFactory抽象
public abstract class FruitFactory{
abstract Fruit getInstance();
}

//创建一个AppleFactory实现FruitFactory
public class AppleFactory extends FruitFactory {
@Override
Fruit getInstance() {
return new Apple();
}
}
public class OrangeFactory extends FruitFactory {
@Override
Fruit getInstance() {
return new Orange();
}
}
//如果要新增产品,需要产品实现Fruit接口并且实现FruitFactory类
public class PearFactory extends FruitFactory {
@Override
Fruit getInstance() {
return new Pear();
}
}

//此时更换新增的需求时我们只需要new一个新的工厂
public class Client {
public static void main(String[] args) {
Fruit fruit=new PearFactory().getInstance();
fruit.eat();
}
}

抽象工厂模式

  说完工厂方法模式,顺便就讲一下抽象工厂模式吧,其实抽象工厂模式是工厂方法模式的一种推广,最明显的一点就是在工厂方法的类关系图中只有一类产品,他们实现了一个统一的接口,而抽象工厂有多个类别的产品,他们分别实现了不同的功能(多个接口)。其次的一点差别就是工厂本身所具有的方法数量不同,这点差异其实也是由第一点所导致的,工厂需要有生产不同类别产品的功能,如果抽象工厂中的产品的功能简化到一个,也便成为了工厂方法。

  再来看选择的过程,在工厂方法中,客户关心的只不过是实现同一样功能的不同产品间的差异,这是一个一维选择的过程。

1
2
IFactory factory = new FactoryA(); //选择了工厂即选择了产品
IProduct productA = factory.Create(); //工厂只有一个Create方法,只能生产一种产品

  而在抽象工厂中,客户首先要考虑的是需要哪一样功能,其次要考虑才是产品间的差异。也就是说这是一个二维选择的过程。

1
2
IFactory factory = new FactoryA(); //选择了某个具体工厂
IProduct productA = factory.CreateProductA(); //工厂具有多个Create方法,这里选择了其中的一个

  由于产品类别的增加而导致客户在考虑产品间差异的同时还要考虑产品间功能的差异,这种二维选择的过程才是工厂方法与抽象工厂之间的本质区别。

  举个肯德基与麦当劳的例子,假设原来只有一家快餐店叫做麦当劳,提供的食物(具体产品)有汉堡、可乐、薯条,它们都可以满足你吃东西(抽象接口)的需求,那么你想吃快餐的时候,唯一的选择就在于吃什么,是一维选择,现在又开了一家快餐店叫做肯德基,同样供应汉堡、可乐和薯条,那么现在你若打算吃快餐,除了考虑吃什么外,还要考虑去哪里吃–肯德基还是麦当劳?这便是二维的选择。通过横向与纵向的选择才能最终锁定你要的产品。

  引入系列的概念,相互间具有差异的同一类别的产品称为不同的系列,如肯德基和麦当劳就是两个不同的系列。

  这种选择的区别带来的另外一个后果就是产品间的差异(系列间的差异)变为客户的次要选择,而客户主要的精力放在了功能的选择上(类别的选择)。

  其实可以这么理解,像工厂方法模式里面出现多个工厂是因为产品品种的变更,每个工厂决定了一个产品品种。而抽象工厂里面不只有一个产品品种,它是一个系列的,也就是所抽象工厂模式里面出现多个工厂是因为系列产品的变更。

反射对于工厂模式的优化

  我们回顾一下刚刚上面所讲的工厂模式,工厂方法模式解决了简单工厂模式的不可拓展性(其实抽象工厂跟工厂方法差不多,这里就以工厂方法模式举例)

  不可拓展的问题是得到解决了,我们再想一下,这样是否是真的完美了吗?当我们要创建一个新的品种的时候我们还需要去创建一个新品种的工厂并且实现原来的基类工厂(FruitFactory),没新增一个我们就要实现一个,这样是不是太麻烦了。这时候,反射给我们提供了很好的解决办法。先看代码:

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 Fruit{
void eat();
}
public class Apple implements Fruit {
@Override
public void eat() {
System.out.println("吃Apple");
}
}
public class Orange implements Fruit {
@Override
public void eat() {
System.out.println("吃Orange");
}
}

//这里创建一个Pear实现Fruit接口
public class Pear implements Fruit {
@Override
public void eat() {
System.out.println("吃Pear");
}
}

public class FruitFactory{
public static Fruit getInstance(String fruitType) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
Fruit fruit=null;
//利用反射
Class<?> clazz=Class.forName(fruitType);
Object object=clazz.newInstance();
fruit=(Fruit)object;
return fruit;
}
}
public class Client {
public static void main(String[] args) throws IllegalAccessException, InstantiationException, ClassNotFoundException {
Fruit fruit=FruitFactory.getInstance("com.lgq.Pear");
fruit.eat();
}
}

  这时候是不是就方便很多了,我们不需要再创建具体类的工厂去实现基类工厂了,我们只需要创建具体类实现基类,然后客户把需求告诉工厂,工厂通过反射创建产品,工厂返回基类就行了。所以最终我们变换的只是那个newInstance方法的参数了,那是一个常量,我们完完全全就可以把它放到配置文件里面。

Spring IOC 和工厂模式

  放到配置文件里面,这有点像什么呢?对了!Spring的IOC。Spring里面有个bean工厂(BeanFactory),其实它也是利用反射的。我们来看一下Spring配置文件里面有个bean节点

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
<bean id="Person" class="test.Person">
<!-- 第一个bean,是一个Person类,id名字随便取,还要写上类的全名 -->
<property name="name">
<value>小龙</value>
<!-- 这里的名字是通过程序里面的set来赋值的,如果去掉程序对应的set,就出错了 -->
</property>

<property name="age">
<value>23</value>
</property>

<property name="grade">
<!-- 这里有点特别,这个grade变量是一个对象,和一般的变量要区别对待 -->
<ref local="Grade"/>
<!-- 这里指向了本配置文件里面一个名字叫Grade(即id=Grade)的bean -->
</property>
</bean>

<bean id="Grade" class="test.Grade"><!-- 同上 -->
<property name="math">
<value>99</value>
</property>
<property name="english">
<value>59</value>
</property>
</bean>

  我们是如何获取的

1
2
3
4
5
6
7
8
9
10
   //加载Spring配置文件
BeanFactory f = new ClassPathXmlApplicationContext("applicationContext.xml");
//从BeanFactory获取对象,通过bean节点的id值,相当于map里面的key class值是value
Object o = f.getBean("Person");
Person person = (Person)o;
Grade grade=(Grade)f.getBean("Grade");
//这时候Spring就为你自动装配了person和grade,且将property里面的值赋过去了
//我们此时可以调用person的get方法
System.out.println(person.getName());
//.......等等

  其实这里面就用到了反射,这个BeanFactory获取到了Spring的配置文件,就会通过某种方法去解析xml文件(好像是SAX,具体还没看),解析了xml文件后,BeanFactory就会获取bean节点的class值,使用这个class值通过反射去创建这个对象并放到容器中去,如果用户需要那么他直接调用getBean方法告诉他我要获取的bean的id值就行了。其实这就是Spring最基本的原理。

反射的一些高级应用

  Java的反射机制的实现要借助于4个类:class,Constructor,Field,Method

  其中class代表的是 类对象,Constructor是类的构造器对象,Field是类的属性对象,Method是类的方法对象。通过这四个对象我们可以粗略的看到一个类的各个组成部分。

  1. 得到构造器的方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //获得使用特殊的参数类型的公共构造函数, 
    Constructor getConstructor(Class[] params)

    //获得类的所有公共构造函数
    Constructor[] getConstructors()

    //获得使用特定参数类型的构造函数(与接入级别无关)
    Constructor getDeclaredConstructor(Class[] params)

    //获得类的所有构造函数(与接入级别无关)
    Constructor[] getDeclaredConstructors()
  2. 获得字段信息(属性)

1
2
3
4
5
6
7
8
9
10
11
//获得命名的公共字段 
Field getField(String name)

//获得类的所有公共字段
Field[] getFields()

//获得类声明的命名的字段
Field getDeclaredField(String name)

//获得类声明的所有字段
Field[] getDeclaredFields()
  1. 获得方法信息
1
2
3
4
5
6
7
8
9
10
11
//使用特定的参数类型,获得命名的公共方法 
Method getMethod(String name, Class[] params)

//获得类的所有公共方法
Method[] getMethods()

//使用特写的参数类型,获得类声明的命名的方法
Method getDeclaredMethod(String name, Class[] params)

//获得类声明的所有方法
Method[] getDeclaredMethods()
-------------本文结束感谢阅读-------------