57.java编程思想——创建窗口和程序片 可视编程和Beans
我们已看到Java 对创建可重复使用的代码片工作而言是多么的有价值。“最大限度地可重复使用”的代码单元拥有类,因为它包含一个紧密结合在一起的单元特性(字段)和单元动作(方法),它们可以直接经过混合或通过继承被重复使用。
继承和多形态性是面向对象编程的精华,但在大多数情况下当我们创建一个应用程序时,我们真正最想要的恰恰是我们最需要的组件。我们希望在我们的设计中设置这些部件就像电子工程师在电路板上创造集成电路块一样(在使用Java 的情况下,就是放到WEB 页面上)。这似乎会成为加快这种“模块集合”编制程序方法的发展。
“可视化编程”最早的成功——非常的成功——要归功于微软公司的Visual Basic(VB,可视化Basic语言),接下来的第二代是Borland 公司Delphi(一种客户/服务器数据库应用程序开发工具,也是JavaBeans 设计的主要灵感)。这些编程工具的组件的像征就是可视化,这是不容置疑的,因为它们通常展示一些类型的可视化组件,例如:一个按惯或一个TextField。事实上,可视化通常表现为组件可以非常精确地访问运行中程序。因此可视化编程方法的一部分包含从一个调色盘从拖放一个组件并将它放置到我们的窗体中。应用程序创建工具像我们所做的一样编写程序代码,该代码将导致正在运行的程序中的组件被创建。
简单地拖放组件到一个窗体中通常不足以构成一个完整的程序。一般情况下,我们需要改变组件的特性,例如组件的色彩,组件的文字,组件连结的数据库,等等。特性可以参照属性在编程时进行修改。我们可以在应用程序构建工具中巧妙处置我们组件的属性,并且当我们创建程序时,构建数据被保存下来,所以当该程序被启动时,数据能被重新恢复。
我们可能习惯于使用对象的多个特性,这也是一个动作集合。在设计时,可视化组件的动作可由事件部分地代表,意味着“任何事件都可以发生在组件上”。通常,由我们决定想发生的事件,当一个事件发生时,对所发生的事件连接代码。
这是关键性的部分:应用程序构建工具可以动态地询问组件(利用映象)以发现组件支持的事件和属件。一旦它知道它们的状态,应用程序构建工具就可以显示组件的属性并允许我们修改它们的属性(当我们构建程序时,保存它们的状态),并且也显示这些事件。一般而言,我们做一些事件像双击一个事件以及应用程序构建工具创建一个代码并连接到事件上。当事件发生时,我们不得不编写执行代码。应用程序构建工具累计为我们做了大量的工作。结果我们可以注意到程序看起来像它所假定的那样运行,并且依赖应用程序构建工具去为我们管理连接的详细资料。可视化的编程工具如此成功的原因是它们明显加快构建的应用程序的处理过程——当然,用户接口作为应用程序的一部分同样的好。
1 什么是B e a n
在经细节处理后,一个组件在类中被独特的具体化,真正地成为一块代码。关键的争议在于应用程序构建工具发现组件的属性和事件能力。为了创建一个VB 组件,程序开发者不得不编写正确的同时也是复杂烦琐的代码片,接下来由某些协议去展现它们的事件和属性。Delphi 是第二代的可视化编程工具并且这种开发语言主动地围绕可视化编程来设计因此它更容易去创建一个可视化组件。但是,Java 带来了可视化的创作组件做为Java Beans 最高级的“装备”,因为一个Bean 就是一个类。我们不必再为制造任何的Bean 而编写一些特殊的代码或者使用特殊的编程语言。事实上,我们唯一需要做的是略微地修改我们对我们方法命名的办法。方法名通知应用程序构建工具是否是一个属性,一个事件或是一个普通的方法。
在Java 的文件中,命名规则被错误地曲解为“设计范式”。这十分的不幸,因为设计范式惹来不少的麻烦。命名规则不是设计范式,它是相当的简单:
(1) 因为属性被命名为xxx,我们代表性的创建两个方法:getXxx()和setXxx()。注意get或set 后的第一个字母小写以产生属性名。“get”和“set”方法产生同样类型的自变量。“set”和“get”的属性名和类型名之间没有关系。
(2) 对于布尔逻辑型属性,我们可以使用上面的“get”和“set”方法,但我们也可以用“is”代替“ get”。
(3) Bean 的普通方法不适合上面的命名规则,但它们是公用的。
对于事件,我们使用“listener (接收器)”方法。这种方法完全同我们看到过的方法相同:(addFooBarListener(FooBarListener)和removeFooBarListener(FooBarListener)方法用来处理FooBar事件。大多数时候内建的事件和接收器会满足我们的需要,但我们可以创建自己的事件和接收器接口。
我们可能注意到的从Java 1.0 到Java 1.1 的改变的问题:一些方法的名字太过于短小,显然改写名字毫无意义。现在我们可以看到为了制造Bean 中的特殊的组件,大多数的这些修改不得不适合于“get”和“set”命名规则。
已经可以利用上面的这些指导方针去创建一个简单的Bean:
1.1 代码
import java.awt.*;
import java.awt.event.*;
class Spots {
}
public class Frog {
private intjumps;
private Color color;
private Spots spots;
private booleanjmpr;
public intgetJumps() {
return jumps;
}
public voidsetJumps(intnewJumps){
jumps = newJumps;
}
public Color getColor() {
return color;
}
public voidsetColor(Color newColor){
color = newColor;
}
public Spots getSpots() {
return spots;
}
public voidsetSpots(Spots newSpots){
spots = newSpots;
}
public booleanisJumper() {
return jmpr;
}
public voidsetJumper(booleanj) {
jmpr = j;
}
public voidaddActionListener(ActionListener l) {
// ...
}
public voidremoveActionListener(ActionListener l) {
// ...
}
public voidaddKeyListener(KeyListener l) {
// ...
}
public voidremoveKeyListener(KeyListener l) {
// ...
}
// An"ordinary" public method:
public voidcroak() {
System.out.println("Ribbet!");
}
} /// :~
首先,我们可看到Bean 就是一个类。通常,所有我们的字段会被作为专用,并且可以接近的唯一办法是通过方法。紧接着的是命名规则,属性是jump,color,jumper,spots(注意这些修改是在第一个字母在属性名的情况下进行的)。虽然内部确定的名字同最早的三个例子的属性名一样,在jumper 中我们可以看到属性名不会强迫我们使用任何特殊的内部可变的名字(或者,真的拥有一些内部的可变的属性名)。
Bean 事件的句柄是ActionEvent和KeyEvent ,这是根据有关接收器的“add”和“remove”命名方法得出的。最后我们可以注意到普通的方法croak()一直是Bean 的一部分,仅仅是因为它是一个公共的方法,而不是因为它符合一些命名规则。
1.2 用I n t r o s p e c to r 提取B e a n I n f o
当我们拖放一个Bean 的调色板并将它放入到窗体中时,一个Bean 的最关键的部分的规则发生了。应用程序构建工具必须可以创建Bean(如果它是默认的构建器的话,它就可以做)然后,在此范围外访问Bean 的源代码,提取所有的必要的信息以创立属性表和事件处理器。
Java 1.1 版的映象允许一个匿名类的所有方法被发现。
这完美地解决了Bean 的难题而无需我们使用一些特殊的语言关键字像在其它的可视化编程语言中所需要的那样。事实上,一个主要的原因是映象增加到Java 1.1 版中以支持Beans(尽管映象同样支持对象串联和远程方法调用)。因为我们可能希望应用程序构建工具的开发者将不得不映象每个Bean 并且通过它们的方法搜索以找到Bean 的属性和事件。
这当然是可能的,但是Java 的研制者们希望为每个使用它的用户提供一个标准的接口,而不仅仅是使Bean更为简单易用,不过他们也同样提供了一个创建更复杂的Bean 的标准方法。这个接口就是Introspector类,在这个类中最重要的方法静态的getBeanInfo()。我们通过一个类处理这个方法并且getBeanInfo()方法全面地对类进行查询,返回一个我们可以进行详细研究以发现其属性、方法和事件的BeanInfo 对象。
通常我们不会留意这样的一些事物——我们可能会使用我们大多数的现成的Bean,并且我们不需要了解所有的在底层运行的技术细节。我们会简单地拖放我们的Bean 到我们窗体中,然后配置它们的属性并且为事件编写处理器。
1.3 代码2
import java.beans.*;
import java.lang.reflect.*;
public class BeanDumper {
public staticvoiddump(Class bean){
BeanInfo bi = null;
try {
bi = Introspector.getBeanInfo(bean, java.lang.Object.class);
} catch (IntrospectionException ex) {
System.out.println("Couldn't introspect "+ bean.getName());
System.exit(1);
}
PropertyDescriptor[] properties = bi.getPropertyDescriptors();
for (int i = 0; i < properties.length; i++) {
Class p = properties[i].getPropertyType();
System.out.println("Property type:\n "+ p.getName());
System.out.println("Property name:\n "+ properties[i].getName());
Method readMethod = properties[i].getReadMethod();
if (readMethod != null)
System.out.println("Read method:\n "+ readMethod.toString());
Method writeMethod = properties[i].getWriteMethod();
if (writeMethod != null)
System.out.println("Write method:\n "+ writeMethod.toString());
System.out.println("====================");
}
System.out.println("Public methods:");
MethodDescriptor[] methods = bi.getMethodDescriptors();
for (int i = 0; i < methods.length; i++)
System.out.println(methods[i].getMethod().toString());
System.out.println("======================");
System.out.println("Event support:");
EventSetDescriptor[] events = bi.getEventSetDescriptors();
for (int i = 0; i < events.length; i++) {
System.out.println("Listener type:\n "+ events[i].getListenerType().getName());
Method[] lm = events[i].getListenerMethods();
for (int j = 0; j < lm.length; j++)
System.out.println("Listener method:\n "+ lm[j].getName());
MethodDescriptor[] lmd = events[i].getListenerMethodDescriptors();
for (int j = 0; j < lmd.length; j++)
System.out.println("Method descriptor:\n " + lmd[j].getMethod().toString());
Method addListener = events[i].getAddListenerMethod();
System.out.println("Add Listener Method:\n " + addListener.toString());
Method removeListener = events[i].getRemoveListenerMethod();
System.out.println("Remove Listener Method:\n " + removeListener.toString());
System.out.println("====================");
}
}
// Dump theclass of your choice:
public staticvoidmain(String[] args){
if (args.length< 1) {
System.err.println("usage: \n"+ "BeanDumperfully.qualified.class");
System.exit(0);
}
Class c = null;
try {
c = Class.forName(args[0]);
} catch (ClassNotFoundException ex) {
System.err.println("Couldn't find "+ args[0]);
System.exit(0);
}
dump(c);
}
} /// :~
BeanDumper.dump()是一个可以做任何工作的方法。首先它试图创建一个BeanInfo 对象,如果成功地调用BeanInfo 的方法,就产生关于属性、方法和事件的信息。在Introspector.getBeanInfo()中,我们会注意到有一个另外的自变量。由它来通知Introspector 访问继承体系的地点。在这种情况下,它在分析所有对象方法前停下,因为我们对看到那些并不感兴趣。
因为属性,getPropertyDescriptors()返回一组的属性描述符号。对于每个描述符号我们可以调用getPropertyType()方法彻底的通过属性方法发现类的对象。这时,我们可以用getName()方法得到每个属性的假名(从方法名中提取),getname()方法用getReadMethod()和getWriteMethod()完成读和写的操作。最后的两个方法返回一个可以真正地用来调用在对象上调用相应的方法方法对象(这是映象的一部分)。对于公共方法(包括属性方法),getMethodDescriptors( )返回一组方法描述字符。每一个我们都可以得到相当的方法对象并可以显示出它们的名字。
对于事件而言,getEventSetDescriptors()返回一组事件描述字符。它们中的每一个都可以被查询以找出接收器的类,接收器类的方法以及增加和删除接收器的方法。BeanDumper 程序打印出所有的这些信息。
Introspector 在从我们的Bean 产生一个BeanInfo 对象时看到的大部分内容。我们可注意
到属性的类型和它们的名字是相互独立的。请注意小写的属性名。(当属性名开头在一行中有超过不止的大写字母,这一次程序就不会被执行。)并且请记住我们在这里所见到的方法名(例如读和与方法)真正地从一个可以被用来在对象中调用相关方法的方法对象中产生。
通用方法列表包含了不相关的事件或者属性,例如croak()。列表中所有的方法都是我们可以有计划的为Bean 调用,并且应用程序构建工具可以选择列出所有的方法,当我们调用方法时,减轻我们的任务。
最后,我们可以看到事件在接收器中完全地分析研究它的方法、增加和减少接收器的方法。基本上,一旦我们拥有BeanInfo ,我们就可以找出对Bean 来说任何重要的事物。我们同样可以为Bean 调用方法,即使我们除了对象外没有任何其它的信息(此外,这也是映象的特点)。