当前位置:七道奇文章资讯编程技术Java编程
日期:2011-03-22 16:16:00  来源:本站整理

用于原型成立的一个范式[Java编程]

赞助商链接



  本文“用于原型成立的一个范式[Java编程]”是由七道奇为您精心收集,来源于网络转载,文章版权归文章作者所有,本站不对其观点以及内容做任何评价,请读者自行判断,以下是其具体内容:
上述计划筹划的一个问题是仍旧需求一此中央场所,必须在那边知道全部范例的对象:在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;
    }
  }
} ///:~

基本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

注意在给定类名的时刻,类途径必须包含在内,不然就找不到类.
为解析它,每一行内容城市读入,并用字串办法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);
  }
} ///:~

全部Trash对象——以及ParseTrash及支持类——目前都成为名为c16.trash的一个包的一部份,所以它们可以简单地导入.
无论翻开包含了Trash描写信息的数据文件,还是对那个文件举行解析,全部触及到的操作均已封装到static(静态)办法ParseTrash.fillBin()里.所以它目前已经不是我们计划历程中要注意的一个重点.在本章剩余的部份,大家常常城市看到无论增添的是什么范例的新类,ParseTrash.fillBin()城市持续工作,不会发生改变,这无疑是一种优异的计划筹划.
提到对象的成立,这一筹划确切已将新范例加入系统所需的变更严峻地“本地化”了.但在利用RTTI的历程中,却存在着一个严重的问题,这里已明确地显暴露来.程序表面上工作得很好,但却永久侦测到不能“硬纸板”(Cardboard)这种新的废品范例——即便列表里确切有一个硬纸板范例!之所以会呈现这种情形,美满是由于利用了RTTI的来由.RTTI只会查找那些我们奉告它查找的东西.RTTI在这里错误的用法是“系统中的每种范例”都举行了测试,而不是仅测试一种范例大概一个范例子集.正如大家今后会看到的那样,在测试每一种范例时可换用其他方法来应用多形性特点.但假定以这种情势过量地利用RTTI,并且又在自己的系统里增添了一种新范例,很简单就会忘掉在程序里作出得当的窜改,从而埋下今后难以发现的Bug.因此,在这种情形下避免利用RTTI是很有必要的,这并不但仅是为了表面好看——也是为了产生更易保护的代码.
  以上是“用于原型成立的一个范式[Java编程]”的内容,如果你对以上该文章内容感兴趣,你可以看看七道奇为您推荐以下文章:
  • 用于原型成立的一个范式
  • 本文地址: 与您的QQ/BBS好友分享!
    • 好的评价 如果您觉得此文章好,就请您
        0%(0)
    • 差的评价 如果您觉得此文章差,就请您
        0%(0)

    文章评论评论内容只代表网友观点,与本站立场无关!

       评论摘要(共 0 条,得分 0 分,平均 0 分) 查看完整评论
    Copyright © 2020-2022 www.xiamiku.com. All Rights Reserved .