日期: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 分)
查看完整评论