在我将要介绍的控制框架(control framework)中,可以看到更多使用内部类的具体例
子。
应用程序框架(application framework)就是被设计用以解决某类特定问题的一个类或
一组类。要使用某个应用程序框架,通常是继承一个或多个类,并重载某些方法。在重载
的方法中,你的代码将应用程序框架提供的通用解决方案特殊化,以解决你的特定问题(这
是设计模式中Template Method 的一个例子。控制框架是一类特殊的应用程序框架,它用来解决
响应事件的需求。主要是用来响应事件的系统被称作事件驱动系统(event-driven
system)。应用程序最重要的问题之一是图形用户接口(GUI),它几乎完全是事件驱动
的系统。在第十四章你会看到,Java Swing 库就是一个控制框架,它优雅地解决了 GUI
的问题,并使用了大量的内部类。
要理解内部类是如何允许简单的创建过程以及如何使用控制框架的,就请你考虑这样一个
控制框架的例子,它的工作就是在事件“就绪(ready)”的时候执行事件。虽然“就绪”
可以指任何事,但在本例中缺省的是基于时间触发的。接下来的问题就是,对于要控制什
么,控制框架并不包含任何具体的信息。那些信息是在实现“模板方法(template
method)”的时候,通过继承来提供的。
首先,接口描述了要控制的事件。因为其缺省的行为是基于时间去执行控制,所以使用抽
象类代替实际的接口。下面的例子包含了某些实现:
//:c08:controller:Event.java
// The commonmethods for any control event.
package c08.controller;
public abstract class Event {
private long eventTime;
protected final long delayTime;
public Event(long delayTime) {
this.delayTime = delayTime;
start();
}
public void start() { // Allows restarting
eventTime = System.currentTimeMillis() +delayTime;
}
public boolean ready() {
return System.currentTimeMillis() >=eventTime;
}
public abstract void action();
} ///:~
如果你希望运行Event,然后调用 start(),那么构造器就会捕获(从对象创建的时刻开始
的)时间,此时间是这样得来的:start()获取当前时间,然后加上一个延迟时间后,生成
触发事件的时间。start()是一个独立的方法,而没有包含在构造器内,因为这样你就可以
在事件运行以后重新启动计时器,也就是能够重复使用 Event 对象。例如,如果你要重复
一个事件,你只需简单地在 action()中调用 start()方法。
ready()告诉你现在可以运行 action()方法了。当然,可以在导出类中重载 ready(),使
得 Event 能够基于时间以外的其它因素而触发。
下面的文件包含了一个实际的管理并触发事件的控制框架。Event 对象被保存在 ArrayList
类型的容器对象中,容器会在第十一章中详细介绍。目前你只需要知道 add()方法将一个
Object 添加到 ArrayList 的尾端,size()方法得到 ArrayList 中元素的个数,get()通过索
引从 ArrayList 中获取一个元素,remove()方法从 ArrayList 中移除一个指定的元素。
//:c08:controller:Controller.java
// With Event,the generic framework for control systems.
package c08.controller;
import java.util.*;
public class Controller {
// An objectfrom java.util to hold Event objects:
private List eventList = new ArrayList();
public void addEvent(Event c) { eventList.add(c); }
public void run() {
while(eventList.size() > 0) {
for(int i = 0; i < eventList.size(); i++) {
Event e = (Event)eventList.get(i);
if(e.ready()) {
System.out.println(e);
e.action();
eventList.remove(i);
}
}
}
}
} ///:~
run()方法循环遍历 eventList,通过 ready()寻找就绪的 Event。对找到的每一个就绪的
事件,使用toString()打印其信息,调用其 action()方法,然后从队列中移除此 Event。
注意,在目前的设计中你并不知道 Event 到底做了什么。这正是此设计的关键之处,“使
变化的事物与不变的事物相互分离”。用我的话说,“变化向量(vector of change)”
就是各种不同的Event 对象所具有的不同行为,而你通过创建不同的 Event 子类来表现不
同的行为。
这正是内部类要做的事情,你可以:
1. 用一个单独的类完整地实现一个控制框架,从而将实现的细节封装起来。内
部类用来表示解决问题所必需的各种不同的 action()。
2. 内部类能够轻易的访问外围类的任意成员,所以可以避免这种实现变得很笨
拙。如果没有这种能力,代码将变得很令人讨厌,以至于你肯定会选择别的
方法。
考虑此控制框架的一个特殊实现,用以控制温室的运作7:控制灯光、水、温度调节器的开
关,以及响铃和重新启动系统,每个行为都是完全不同的。控制框架的设计使得分离这些
不同的代码变得非常容易。使用内部类,可以在单一的类里面产生对同一个基类Event的多
种继承版本。对于温室系统的每一种行为,都继承一个新的Event内部类,并在要实现的
action()中编写控制代码。
作为典型的应用程序框架,GreenhouseControls 类继承自 Controller:
//:c08:GreenhouseControls.java
// Thisproduces a specific application of the
// controlsystem, all in a single class. Inner
// classesallow you to encapsulate different
//functionality for each type of event.
import com.bruceeckel.simpletest.*;
import c08.controller.*;
public class GreenhouseControlsextends Controller {
private static Test monitor = new Test();
private boolean light = false;
public class LightOn extends Event {
public LightOn(long delayTime) {super(delayTime); }
public void action() {
// Put hardwarecontrol code here to
// physicallyturn on the light.
light = true;
}
public String toString() { return "Light is on"; }
}
public class LightOff extends Event {
public LightOff(long delayTime) {super(delayTime); }
public void action() {
// Put hardwarecontrol code here to
// physicallyturn off the light.
light = false;
}
public String toString() { return "Light is off"; }
}
private boolean water = false;
public class WaterOn extends Event {
public WaterOn(long delayTime) {super(delayTime); }
public void action() {
// Put hardwarecontrol code here.
water = true;
}
public String toString() {
return "Greenhouse water is on";
}
}
public class WaterOff extends Event {
public WaterOff(long delayTime) {super(delayTime); }
public void action() {
// Put hardwarecontrol code here.
water = false;
}
public String toString() {
return "Greenhouse water is off";
}
}
private String thermostat = "Day";
public class ThermostatNight extends Event {
public ThermostatNight(long delayTime) {
super(delayTime);
}
public void action() {
// Put hardwarecontrol code here.
thermostat = "Night";
}
public String toString() {
return "Thermostat on night setting";
}
}
public class ThermostatDay extends Event {
public ThermostatDay(long delayTime) {
}
super(delayTime);
public void action() {
// Put hardwarecontrol code here.
thermostat = "Day";
}
public String toString() {
return "Thermostat on day setting";
}
}
// An exampleof an action() that inserts a
// new one ofitself into the event list:
public class Bell extends Event {
public Bell(long delayTime) { super(delayTime); }
public void action() {
addEvent(new Bell(delayTime));
}
public String toString() { return "Bing!"; }
}
public class Restart extends Event {
private Event[] eventList;
public Restart(long delayTime, Event[] eventList) {
super(delayTime);
this.eventList = eventList;
for(int i = 0; i < eventList.length; i++)
addEvent(eventList[i]);
}
public void action() {
for(int i = 0; i < eventList.length; i++) {
eventList[i].start(); // Rerun each event
addEvent(eventList[i]);
}
start(); // Rerun this Event
addEvent(this);
}
public String toString() {
return "Restarting system";
}
}
public class Terminate extends Event {
public Terminate(long delayTime) {super(delayTime); }
public void action() { System.exit(0); }
public String toString() { return "Terminating"; }
}
} ///:~
注意,light、 water 和 thermostat 都属于外围类,而这些内部类能够自由地访问那些
成员变量,无需限定条件或特殊许可。而且,大多数 action()方法都涉及对某种硬件的控
制。
大多数 Event 类看起来都很相似,但是 Bell 和 Restart 则比较特别。Bell 控制响铃,然后
在事件列表中增加一个 Bell 对象,于是过一会儿它可以再次响铃。你可能注意到了内部类
是多么像的多重继承:Bell 和 Restart 有 Event 的所有方法,并且似乎也拥有外围类
GreenhouseContrlos所有的方法。
一个 Event 对象的数组被地交给 Restart,该数组要加到控制器上的。由于 Restart()也
是一个 Event 对象,所以你同样可以将 Restart 对象添加到 Restart.action()中,以使系
统能够有规律的重新启动它自己。
下面的类通过创建一个 GreenhouseControls 对象,并添加各种不同的 Event 对象来配
置该系统。这是命令(Command)设计模式的一个例子:
//:c08:GreenhouseController.java
// Configureand execute the greenhouse system.
// {Args: 5000}
import c08.controller.*;
public class GreenhouseController {
public static void main(String[] args) {
GreenhouseControls gc = new GreenhouseControls();
// Instead ofhard-wiring, you could parse
//configuration information from a text file here:
gc.addEvent(gc.new Bell(900));
Event[] eventList = {
gc.new ThermostatNight(0),
gc.new LightOn(200),
gc.new LightOff(400),
gc.new WaterOn(600),
gc.new WaterOff(800),
gc.new ThermostatDay(1400)
};
gc.addEvent(gc.new Restart(2000, eventList));
if(args.length == 1)
gc.addEvent(
gc.new Terminate(Integer.parseInt(args[0])));
gc.run();
}
} ///:~
这个类的作用是初始化系统,所以它添加了所有相应的事件。当然,更灵活的方法是避免
对事件进行硬编码,取而代之的是从文件中读取需要的事件。(第十二章的练习会要求你
照此方法修改这个例子。)如果你提供了命令行参数,系统会以它作为毫秒数,决定什么
时候终止程序(这是测试程序时使用的)。
这个例子应该能使你更了解内部类的价值,特别是在控制框架中使用内部类的时候。而在
第十四章中,你将看到内部类如何优雅地描述图形用户界面的行为。到那时,你应该就完
全信服内部类的价值了。