用于原型建设的一个范式
当前位置:以往代写 > JAVA 教程 >用于原型建设的一个范式
2019-06-14

用于原型建设的一个范式

用于原型建设的一个范式

上述设计方案的一个问题是仍然需要一其中心场合,必需在哪里知道所有范例的工具:在factory()要领内部。假如常常都要向系统添加新范例,factory()要领为每种新范例都要修改一遍。若确实对这个问题感想苦恼,可试试再深入一步,将与范例有关的所有信息——包罗它的建设进程——都移入代表那种范例的类内部。这样一来,每次新添一种范例的时候,需要做的独一工作就是从一个类担任。
为将涉及范例建设的信息移入特定范例的Trash里,必需利用“原型”(prototype)范式(来自《Design Patterns》那本书)。这里最根基的想法是我们有一个主控工具序列,为本身感乐趣的每种范例都建造一个。这个序列中的工具只能用于新工具的建设,回收的操纵雷同内建到Java根类Object内部的clone()机制。在这种环境下,我们将克隆要领定名为tClone()。筹备建设一个新工具时,要事先收集好某种形式的信息,用它成立我们但愿的工具范例。然后在主控序列中遍历,将手上的信息与主控序列华夏型工具内任何适当的信息作比拟。若找到一个切合本身需要的,就克隆它。
回收这种方案,我们不必用硬编码的方法植入任何建设信息。每个工具都知道如何展现出适当的信息,以及如何对自身举办克隆。所以一种新范例插手系统的时候,factory()要领不需要任何改变。
为办理原型的建设问题,一个要领是添加大量要领,用它们支持新工具的建设。但在Java 1.1中,假如拥有指向Class工具的一个句柄,那么它已经提供了对建设新工具的支持。操作Java 1.1的“反射”(已在第11章先容)技能,即便我们只有指向Class工具的一个句柄,亦可正常地挪用一个构建器。这对原型问题的办理无疑是个完美的方案。
原型列表将由指向所有想建设的Class工具的一个句柄列表间接地暗示。除此之外,如果原型处理惩罚失败,则factory()要了解认为由于一个特定的Class工具不在列表中,所以会实验装载它。通过以这种方法动态装载原型,Trash类基础不需要知道本身要哄骗的是什么范例。因此,在我们添加新范例时不需要作出任何形式的修改。于是,我们可在本章剩余的部门利便地反复操作它。

 

//: Trash.java
// Base class for Trash recycling examples
package c16.trash;
import java.util.*;
import java.lang.reflect.*;

public abstract class Trash {
  private double weight;
  Trash(double wt) { weight = wt; }
  Trash() {}
  public abstract double value();
  public double weight() { return weight; }
  // Sums the value of Trash in a bin:
  public static void sumValue(Vector bin) {
    Enumeration e = bin.elements();
    double val = 0.0f;
    while(e.hasMoreElements()) {
      // One kind of RTTI:
      // A dynamically-checked cast
      Trash t = (Trash)e.nextElement();
      val += t.weight() * t.value();
      System.out.println(
        "weight of " +
        // Using RTTI to get type
        // information about the class:
        t.getClass().getName() +
        " = " + t.weight());
    }
    System.out.println("Total value = " + val);
  }
  // Remainder of class provides support for
  // prototyping:
  public static class PrototypeNotFoundException
      extends Exception {}
  public static class CannotCreateTrashException
      extends Exception {}
  private static Vector trashTypes = 
    new Vector();
  public static Trash factory(Info info) 
      throws PrototypeNotFoundException, 
      CannotCreateTrashException {
    for(int i = 0; i < trashTypes.size(); i++) {
      // Somehow determine the new type
      // to create, and create one:
      Class tc = 
        (Class)trashTypes.elementAt(i);
      if (tc.getName().indexOf(info.id) != -1) {
        try {
          // Get the dynamic constructor method
          // that takes a double argument:
          Constructor ctor =
            tc.getConstructor(
              new Class[] {double.class});
          // Call the constructor to create a 
          // new object:
          return (Trash)ctor.newInstance(
            new Object[]{new Double(info.data)});
        } catch(Exception ex) {
          ex.printStackTrace();
          throw new CannotCreateTrashException();
        }
      }
    }
    // Class was not in the list. Try to load it,
    // but it must be in your class path!
    try {
      System.out.println("Loading " + info.id);
      trashTypes.addElement(
        Class.forName(info.id));
    } catch(Exception e) {
      e.printStackTrace();
      throw new PrototypeNotFoundException();
    }
    // Loaded successfully. Recursive call 
    // should work this time:
    return factory(info);
  }
  public static class Info {
    public String id;
    public double data;
    public Info(String name, double data) {
      id = name;
      this.data = data;
    }
  }
} ///:~

#p#分页标题#e#

根基Trash类和sumValue()照旧象往常一样。这个类剩下的部门支持原型范式。各人首先会看到两个内部类(被设为static属性,使其成为只为代码组织目标而存在的内部类),它们描写了大概呈现的违例。在它后头跟从的是一个Vector trashTypes,用于容纳Class句柄。
在Trash.factory()中,Info工具id(Info类的另一个版本,与前面接头的差异)内部的String包括了要建设的那种Trash的范例名称。这个String会与列表中的Class名较量。若存在相符的,那即是要建设的工具。虽然,尚有许多要领可以抉择我们想建设的工具。之所以要回收这种要领,是因为从一个文件读入的信息可以转换成工具。
发明本身要建设的Trash(垃圾)种类后,接下来就轮到“反射”要领大显身手了。getConstructor()要领需要取得本身的参数——由Class句柄组成的一个数组。这个数组代表着差异的参数,并按它们正确的顺序分列,以便我们查找的构建器利用。在这儿,该数组是用Java 1.1的数组建设语法动态建设的:
new Class[] {double.class}
这个代码假定所有Trash范例都有一个需要double数值的构建器(留意double.class与Double.class是差异的)。若思量一种更机动的方案,亦可挪用getConstructors(),令其返回可用构建器的一个数组。
从getConstructors()返回的是指向一个Constructor工具的句柄(该工具是java.lang.reflect的一部门)。我们用要领newInstance()动态地挪用构建器。该要领需要获取包括了实际参数的一个Object数组。这个数组同样是按Java 1.1的语法建设的:
new Object[] {new Double(info.data)}
在这种环境下,double必需置入一个封装(容器)类的内部,使其真正成为这个工具数组的一部门。通过挪用newInstance(),会提取出double,但各人大概会以为稍微有些疑惑——参数既大概是double,也大概是Double,但在挪用的时候必需用Double通报。幸运的是,这个问题只存在于根基数据范例中间。
领略了详细的进程后,再来建设一个新工具,而且只为它提供一个Class句柄,工作就变得很是简朴了。就今朝的环境来说,内部轮回中的return永远不会执行,我们在终点就会退出。在这儿,措施动态装载Class工具,并把它插手trashTypes(垃圾范例)列表,从而试图更正这个问题。若仍然找不到真正有问题的处所,同时装载又是乐成的,那么就反复挪用factory要领,从头试一遍。
正如各人会看到的那样,这种设计方案最大的利益就是不需要窜改代码。无论在什么环境下,它都能正常地利用(假定所有Trash子类都包括了一个构建器,用以获取单个double参数)。

1. Trash子类
为了与原型机制相适应,对Trash每个新子类独一的要求就是在个中包括了一个构建器,指示它获取一个double参数。Java 1.1的“反射”机制可认真剩下的所有事情。
下面是差异范例的Trash,每种范例都有它们本身的文件里,但都属于Trash包的一部门(同样地,为了利便在本章内反复利用):

 

//: Aluminum.java 
// The Aluminum class with prototyping
package c16.trash;

public class Aluminum extends Trash {
  private static double val = 1.67f;
  public Aluminum(double wt) { super(wt); }
  public double value() { return val; }
  public static void value(double newVal) {
    val = newVal;
  }
} ///:~

下面是一种新的Trash范例:

 

//: Cardboard.java 
// The Cardboard class with prototyping
package c16.trash;

public class Cardboard extends Trash {
  private static double val = 0.23f;
  public Cardboard(double wt) { super(wt); }
  public double value() { return val; }
  public static void value(double newVal) {
    val = newVal;
  }
} ///:~

可以看出,除构建器以外,这些类基础没有什么出格的处所。

2. 从外部文件中理会出Trash
与Trash工具有关的信息将从一个外部文件中读取。针对Trash的每个方面,文件内列出了所有须要的信息——每行都代表一个方面,回收“垃圾(废品)名称:值”的牢靠名目。譬喻:

 

c16.Trash.Glass:54
c16.Trash.Paper:22
c16.Trash.Paper:11
c16.Trash.Glass:17
c16.Trash.Aluminum:89
c16.Trash.Paper:88
c16.Trash.Aluminum:76
c16.Trash.Cardboard:96
c16.Trash.Aluminum:25
c16.Trash.Aluminum:34
c16.Trash.Glass:11
c16.Trash.Glass:68
c16.Trash.Glass:43
c16.Trash.Aluminum:27
c16.Trash.Cardboard:44
c16.Trash.Aluminum:18
c16.Trash.Paper:91
c16.Trash.Glass:63
c16.Trash.Glass:50
c16.Trash.Glass:80
c16.Trash.Aluminum:81
c16.Trash.Cardboard:12
c16.Trash.Glass:12
c16.Trash.Glass:54
c16.Trash.Aluminum:36
c16.Trash.Aluminum:93
c16.Trash.Glass:93
c16.Trash.Paper:80
c16.Trash.Glass:36
c16.Trash.Glass:12
c16.Trash.Glass:60
c16.Trash.Paper:66
c16.Trash.Aluminum:36
c16.Trash.Cardboard:22

#p#分页标题#e#

留意在给定类名的时候,类路径必需包括在内,不然就找不到类。
为理会它,每一行内容城市读入,并用字串要领indexOf()来成立“:”的一个索引。首先用字串要领substring()取出垃圾的范例名称,接着用一个静态要领Double.valueOf()取得相应的值,并转换成一个double值。trim()要领例用于删除字串两端的多余空格。
Trash理会器置入单独的文件中,因为本章将不绝地用到它。如下所示:

 

//: ParseTrash.java 
// Open a file and parse its contents into
// Trash objects, placing each into a Vector
package c16.trash;
import java.util.*;
import java.io.*;

public class ParseTrash {
  public static void 
  fillBin(String filename, Fillable bin) {
    try {
      BufferedReader data =
        new BufferedReader(
          new FileReader(filename));
      String buf;
      while((buf = data.readLine())!= null) {
        String type = buf.substring(0, 
          buf.indexOf(':')).trim();
        double weight = Double.valueOf(
          buf.substring(buf.indexOf(':') + 1)
          .trim()).doubleValue();
        bin.addTrash(
          Trash.factory(
            new Trash.Info(type, weight)));
      }
      data.close();
    } catch(IOException e) {
      e.printStackTrace();
    } catch(Exception e) {
      e.printStackTrace();
    }
  }
  // Special case to handle Vector:
  public static void 
  fillBin(String filename, Vector bin) {
    fillBin(filename, new FillableVector(bin));
  }
} ///:~

在RecycleA.java中,我们用一个Vector容纳Trash工具。然而,亦可思量回收其他荟萃范例。为做到这一点,fillBin()的第一个版本将获取指向一个Fillable的句柄。后者是一个接口,用于支持一个名为addTrash()的要领:

 

//: Fillable.java 
// Any object that can be filled with Trash
package c16.trash;

public interface Fillable {
  void addTrash(Trash t);
} ///:~

支持该接口的所有对象都能陪伴fillBin利用。虽然,Vector并未实现Fillable,所以它不能事情。由于Vector将在大大都例子中应用,所以最好的做法是添加另一个过载的fillBin()要领,令其以一个Vector作为参数。操作一个适配器(Adapter)类,这个Vector可作为一个Fillable工具利用:

 

//: FillableVector.java 
// Adapter that makes a Vector Fillable
package c16.trash;
import java.util.*;

public class FillableVector implements Fillable {
  private Vector v;
  public FillableVector(Vector vv) { v = vv; }
  public void addTrash(Trash t) {
    v.addElement(t);
  }
} ///:~

可以看到,这个类独一的任务就是认真将Fillable的addTrash()同Vector的addElement()要领毗连起来。操作这个类,已过载的fillBin()要领可在ParseTrash.java中陪伴一个Vector利用:

 

  public static void 
  fillBin(String filename, Vector bin) {
    fillBin(filename, new FillableVector(bin));
  }

这种方案合用于任何频繁用到的荟萃类。除此以外,荟萃类还可提供它本身的适配器类,并实现Fillable(稍后即可看到,在DynaTrash.java中)。

3. 原型机制的反复应用
此刻,各人可以看到回收原型技能的、修订过的RecycleA.java版本了:

 

//: RecycleAP.java 
// Recycling with RTTI and Prototypes
package c16.recycleap;
import c16.trash.*;
import java.util.*;

public class RecycleAP {
  public static void main(String[] args) {
    Vector bin = new Vector();
    // Fill up the Trash bin:
    ParseTrash.fillBin("Trash.dat", bin);
    Vector 
      glassBin = new Vector(),
      paperBin = new Vector(),
      alBin = new Vector();
    Enumeration sorter = bin.elements();
    // Sort the Trash:
    while(sorter.hasMoreElements()) {
      Object t = sorter.nextElement();
      // RTTI to show class membership:
      if(t instanceof Aluminum)
        alBin.addElement(t);
      if(t instanceof Paper)
        paperBin.addElement(t);
      if(t instanceof Glass)
        glassBin.addElement(t);
    }
    Trash.sumValue(alBin);
    Trash.sumValue(paperBin);
    Trash.sumValue(glassBin);
    Trash.sumValue(bin);
  }
} ///:~

#p#分页标题#e#

所有Trash工具——以及ParseTrash及支撑类——此刻都成为名为c16.trash的一个包的一部门,所以它们可以简朴地导入。
无论打开包括了Trash描写信息的数据文件,照旧对谁人文件举办理会,所有涉及到的操纵均已封装到static(静态)要领ParseTrash.fillBin()里。所以它此刻已经不是我们设计进程中要留意的一个重点。在本章剩余的部门,各人常常城市看到无论添加的是什么范例的新类,ParseTrash.fillBin()城市一连事情,不会产生改变,这无疑是一种优良的设计方案。
提到工具的建设,这一方案确实已将新范例插手系统所需的变换严格地“当地化”了。但在利用RTTI的进程中,却存在着一个严重的问题,这里已明晰地显暴露来。措施外貌上事情得很好,但却永远侦测到不能“硬纸板”(Cardboard)这种新的废品范例——纵然列内外确实有一个硬纸板范例!之所以会呈现这种环境,完全是由于利用了RTTI的缘故。RTTI只会查找那些我们汇报它查找的对象。RTTI在这里错误的用法是“系统中的每种范例”都举办了测试,而不是仅测试一种范例可能一个范例子集。正如各人今后会看到的那样,在测试每一种范例时可换用其他方法来运用多形性特征。但如果以这种形式过多地利用RTTI,并且又在本身的系统里添加了一种新范例,很容易就会健忘在措施里作出适当的窜改,从而埋下今后难以发明的Bug。因此,在这种环境下制止利用RTTI是很有须要的,这并不只仅是为了外貌悦目——也是为了发生更易维护的代码。

    关键字:

在线提交作业