设计模式——工厂方法&模版方法

  Java的泛型一直是我比较感兴趣的部分,但是既然说起泛型,就不得不提到擦除。Java泛型是使用擦除实现的,使用泛型时,具体的类型信息都被“擦除”了。举个例子:List<String>和List<Integer>在运行时实际上都是相同的类型,都被擦除成了“原生的”类型,即List。

  泛型类型参数将擦除到它的第一个边界,如List<T>将被擦除成List,而普通的类型变量在未指定边界的情况下被擦除成Object。

 1 package com.test.generic;
 2 
 3 public class Erased<T> {
 4     private final int SIZE = 100;
 5     public static void f(Object arg) {
 6         if(arg instanceof T) {} //编译报错
 7         T var = new T(); //编译报错
 8         T[] array = new T[SIZE]; //编译报错
 9         T[] array = new Onject[SIZE]; //编译报错
10     }
11 }
View Code

工厂方法

  在上面的代码中,创建一个new T()的操作无法实现,部分原因就是擦除,另一部分原因是因为编译器不能验证码T具有默认(无参)的构造器。Java的解决方案是传递一个工厂对象,并使用它来创建新的实例。最便利的工厂对象就是Class对象,如下代码所示:

 1 package com.test.generic;
 2 
 3 class ClassAsFactory<T> {
 4     T x;
 5     public ClassAsFactory(Class<T> kind) {
 6         try {
 7             x  = kind.newInstance();
 8         } catch (Exception e) {
 9             throw new RuntimeException(e);
10         }
11     }
12 }
13 
14 class Employee {}
15 
16 public class InstantiateGenericType {
17 
18     public static void main(String[] args) {
19         ClassAsFactory<Employee> fe = new ClassAsFactory<Employee>(Employee.class);
20         System.out.println("ClassAsFactory<Employee> succeeded");
21         
22         try {
23             ClassAsFactory<Integer> fi = new ClassAsFactory<Integer>(Integer.class);
24         } catch (Exception e) {
25             System.out.println("ClassAsFactory<Employee> failed");
26         }
27     }
28 
29 }
View Code

运行结果如下

从运行结果看到,可以编译,但是会因为ClassAsFactory<integer>而失败,因为Integer没有任何默认的构造器。因为这个错误不是在编译期捕获的。Sun公司建议使用显式的工厂,并限制类型,只能接受实现了这工厂的类,如下代码所示:

 1 package com.test.generic;
 2 
 3 interface FactoryI<T> {
 4     T create();
 5 }
 6 
 7 class Foo2<T> {
 8     private T x;
 9     public <F extends FactoryI<T>> Foo2(F factory) {
10         x = factory.create();
11     }
12 }
13 
14 class IntegerFactory implements FactoryI<Integer> {
15     public Integer create() {
16         return new Integer(0);
17     }
18 }
19 
20 class Widget {
21     public static class Factory implements FactoryI<Widget> {
22         @Override
23         public Widget create() {
24             return new Widget();
25         }
26     }
27 }
28 public class FactoryConstraint {
29     public static void main(String[] args) {
30         new Foo2<Integer>(new IntegerFactory());
31         new Foo2<Widget>(new Widget.Factory());
32     }
33 }
View Code

Class<T>碰巧是创建内建的工厂对象,而通过上面那样显示的创建一个工厂对象,可以获得编译期的检查。

模版方法:

  另一种方法是使用模版方法设计模式,通过实现抽象类(模版)的抽象方法来创建对象,同样能获得编译期的类型检查,下面是代码:

 1 package com.test.generic;
 2 
 3 abstract class GenericWithCreate<T> {
 4     final T element;
 5     GenericWithCreate() {
 6         element = create();
 7     }
 8     abstract T create();
 9 }
10 
11 class X {}
12 
13 class Creator extends GenericWithCreate<X> {
14     @Override
15     X create() {
16         return new X();
17     }
18     void f() {
19         System.out.println(element.getClass().getSimpleName());
20     }
21 }
22 public class CreatorGeneric {
23     public static void main(String[] args) {
24         Creator c = new Creator();
25         c.f();
26     }
27 }    
View Code

总结:

  在使用泛型的过程中,因为擦除的存在,任何在运行时需要知道确切类型信息的操作都没法好好工作。通过反射配合工厂方法或者模版方法的使用,我们能在编译器就知道创建对象时传递的参数类型是否正确。

posted @ 2017-10-04 22:45  night_joe  阅读(890)  评论(0编辑  收藏  举报