1. 程式人生 > >Java設計模式——工廠方法&模版方法

Java設計模式——工廠方法&模版方法

ctype sys 通過反射 ets gif pla ons view close

  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

總結:

  在使用泛型的過程中,因為擦除的存在,任何在運行時需要知道確切類型信息的操作都沒法好好工作。通過反射配合工廠方法或者模版方法的使用,我們能在編譯器就知道創建對象時傳遞的參數類型是否正確。

Java設計模式——工廠方法&模版方法