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


      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

    总结:

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

  • 相关阅读:
    C#撸了个批量转换Word、Excel、PPT为PDF的软件 pdfcvt.com
    关于《图解国富论》若干问题的思考《六》
    关于《图解国富论》若干问题的思考《五》
    关于《图解国富论》若干问题的思考《四》
    关于《图解国富论》若干问题的思考《三》
    关于《图解国富论》若干问题的思考《二》
    关于《图解国富论》若干问题的思考《一》
    忆秦娥·娄山关
    《动物精神》笔记
    《非理性繁荣》笔记
  • 原文地址:https://www.cnblogs.com/junqiao/p/7627604.html
Copyright © 2020-2023  润新知