为什么要用内部类:节制框架
到今朝为止,各人已打仗了对内部类的运作举办描写的大量语法与观念。但这些并不能真正说明内部类存在的原因。为什么Sun要如此贫苦地在Java 1.1里添加这样的一种根基语言特性呢?谜底就在于我们在这里要进修的“节制框架”。
一个“应用措施框架”是指一个或一系列类,它们专门设计用来办理特定范例的问题。为应用应用措施框架,我们可从一个或多个类担任,并包围个中的部门要领。我们在包围要领中编写的代码用于定制由那些应用措施框架提供的通例方案,以便办理本身的实际问题。“节制框架”属于应用措施框架的一种非凡范例,受到对事件响应的需要的支配;主要用来响应事件的一个系统叫作“由事件驱动的系统”。在应用措施设计语言中,最重要的问题之一即是“图形用户界面”(GUI),它险些完全是由事件驱动的。正如各人会在第13章进修的那样,Java 1.1 AWT属于一种节制框架,它通过内部类完美地办理了GUI的问题。
为领略内部类如何简化节制框架的建设与利用,可认为一个节制框架的事情就是在事件“停当”今后执行它们。尽量“停当”的意思许多,但在今朝这种环境下,我们却是以计较机时钟为基本。随后,请认识到针对节制框架需要节制的对象,框架内并未包括任何特定的信息。首先,它是一个非凡的接口,描写了所有节制事件。它可以是一个抽象类,而非一个实际的接口。由于默认行为是按照时间节制的,所以部门实施细节大概包罗:
//: Event.java
// The common methods for any control event
package c07.controller;
abstract public class Event {
private long evtTime;
public Event(long eventTime) {
evtTime = eventTime;
}
public boolean ready() {
return System.currentTimeMillis() >= evtTime;
}
abstract public void action();
abstract public String description();
} ///:~
但愿Event(事件)运行的时候,构建器即简朴地捕捉时间。同时ready()汇报我们何时该运行它。虽然,ready()也可以在一个衍生类中被包围,将事件成立在除时间以外的其他对象上。
action()是事件停当后需要挪用的要领,而description()提供了与事件有关的文字信息。
下面这个文件包括了实际的节制框架,用于打点和触发事件。第一个类实际只是一个“助手”类,它的职责是容纳Event工具。可用任何适当的荟萃替换它。并且通过第8章的进修,各人会知道另一些荟萃可简化我们的事情,不需要我们编写这些特另外代码:
//: Controller.java
// Along with Event, the generic
// framework for all control systems:
package c07.controller;
// This is just a way to hold Event objects.
class EventSet {
private Event[] events = new Event[100];
private int index = 0;
private int next = 0;
public void add(Event e) {
if(index >= events.length)
return; // (In real life, throw exception)
events[index++] = e;
}
public Event getNext() {
boolean looped = false;
int start = next;
do {
next = (next + 1) % events.length;
// See if it has looped to the beginning:
if(start == next) looped = true;
// If it loops past start, the list
// is empty:
if((next == (start + 1) % events.length)
&& looped)
return null;
} while(events[next] == null);
return events[next];
}
public void removeCurrent() {
events[next] = null;
}
}
public class Controller {
private EventSet es = new EventSet();
public void addEvent(Event c) { es.add(c); }
public void run() {
Event e;
while((e = es.getNext()) != null) {
if(e.ready()) {
e.action();
System.out.println(e.description());
es.removeCurrent();
}
}
}
} ///:~
EventSet可容纳100个事件(若在这里利用来自第8章的一个“真实”荟萃,就不必担忧它的最大尺寸,因为它会按照环境自动改变巨细)。index(索引)在这里用于跟踪下一个可用的空间,而next(下一个)辅佐我们寻找列表中的下一个事件,相识本身是否已经轮回到头。在对getNext()的挪用中,这一点是至关重要的,因为一旦运行,Event工具就会从列表中删去(利用removeCurrent())。所以getNext()会在列表中向前移动时碰着“空洞”。
留意removeCurrent()并不可是指示一些符号,指出工具不再利用。相反,它将句柄设为null。这一点长短常重要的,因为如果垃圾收集器发明一个句柄仍在利用,就不会排除工具。若认为本身的句柄大概象此刻这样被挂起,那么最好将其设为null,使垃圾收集器可以或许正常地排除它们。
Controller是举办实际事情的处所。它用一个EventSet容纳本身的Event工具,并且addEvent()答允我们向这个列表插手新事件。但最重要的要领是run()。该要了解在EventSet中遍历,搜索一个筹备运行的Event工具——ready()。对付它发明ready()的每一个工具,城市挪用action()要领,打印出description(),然后将事件从列表中删去。
留意在迄今为止的所有设计中,我们仍然不能精确地知道一个“事件”要做什么。这正是整个设计的要害;它奈何“将产生变革的对象同没有变革的对象区分隔”?可能用我的话来讲,“改变的意图”造成了种种Event工具的差异动作。我们通过建设差异的Event子类,从而表达出差异的动作。
这里正是内部类大显身手的处所。它们答允我们做两件工作:
(1) 在单唯一个类里表达一个节制框架应用的全部实施细节,从而完整地封装与谁人实施有关的所有对象。内部类用于表达多种差异范例的action(),它们用于办理实际的问题。除此以外,后续的例子利用了private内部类,所以实施细节会完全埋没起来,可以安详地修改。
(2) 内部类使我们详细的实施变得越发巧妙,因为能利便地会见外部类的任何成员。若不具备这种本领,代码看起来就大概没那么使人舒服,最后不得不寻找其他要领办理。
此刻要请各人思考节制框架的一种详细实施方法,它设计用来节制温室(Greenhouse)成果(注释④)。每个动作都是完全差异的:节制灯光、供水以及温度自动调理的开与关,节制响铃,以及从头启动系统。但节制框架的设计宗旨是将差异的代码利便地隔分开。对每种范例的动作,都要担任一个新的Event内部类,并在action()内编写相应的节制代码。
④:由于某些非凡原因,这对我来说是一个常常需要办理的、很是有趣的问题;本来的例子在《C++ Inside & Out》一书里也呈现过,但Java提供了一种更令人舒适的办理方案。
作为应用措施框架的一种典范行为,GreenhouseControls类是从Controller担任的:
//: GreenhouseControls.java
// This produces a specific application of the
// control system, all in a single class. Inner
// classes allow you to encapsulate different
// functionality for each type of event.
package c07.controller;
public class GreenhouseControls
extends Controller {
private boolean light = false;
private boolean water = false;
private String thermostat = "Day";
private class LightOn extends Event {
public LightOn(long eventTime) {
super(eventTime);
}
public void action() {
// Put hardware control code here to
// physically turn on the light.
light = true;
}
public String description() {
return "Light is on";
}
}
private class LightOff extends Event {
public LightOff(long eventTime) {
super(eventTime);
}
public void action() {
// Put hardware control code here to
// physically turn off the light.
light = false;
}
public String description() {
return "Light is off";
}
}
private class WaterOn extends Event {
public WaterOn(long eventTime) {
super(eventTime);
}
public void action() {
// Put hardware control code here
water = true;
}
public String description() {
return "Greenhouse water is on";
}
}
private class WaterOff extends Event {
public WaterOff(long eventTime) {
super(eventTime);
}
public void action() {
// Put hardware control code here
water = false;
}
public String description() {
return "Greenhouse water is off";
}
}
private class ThermostatNight extends Event {
public ThermostatNight(long eventTime) {
super(eventTime);
}
public void action() {
// Put hardware control code here
thermostat = "Night";
}
public String description() {
return "Thermostat on night setting";
}
}
private class ThermostatDay extends Event {
public ThermostatDay(long eventTime) {
super(eventTime);
}
public void action() {
// Put hardware control code here
thermostat = "Day";
}
public String description() {
return "Thermostat on day setting";
}
}
// An example of an action() that inserts a
// new one of itself into the event list:
private int rings;
private class Bell extends Event {
public Bell(long eventTime) {
super(eventTime);
}
public void action() {
// Ring bell every 2 seconds, rings times:
System.out.println("Bing!");
if(--rings > 0)
addEvent(new Bell(
System.currentTimeMillis() + 2000));
}
public String description() {
return "Ring bell";
}
}
private class Restart extends Event {
public Restart(long eventTime) {
super(eventTime);
}
public void action() {
long tm = System.currentTimeMillis();
// Instead of hard-wiring, you could parse
// configuration information from a text
// file here:
rings = 5;
addEvent(new ThermostatNight(tm));
addEvent(new LightOn(tm + 1000));
addEvent(new LightOff(tm + 2000));
addEvent(new WaterOn(tm + 3000));
addEvent(new WaterOff(tm + 8000));
addEvent(new Bell(tm + 9000));
addEvent(new ThermostatDay(tm + 10000));
// Can even add a Restart object!
addEvent(new Restart(tm + 20000));
}
public String description() {
return "Restarting system";
}
}
public static void main(String[] args) {
GreenhouseControls gc =
new GreenhouseControls();
long tm = System.currentTimeMillis();
gc.addEvent(gc.new Restart(tm));
gc.run();
}
} ///:~
#p#分页标题#e#
留意light(灯光)、water(供水)、thermostat(调温)以及rings都附属于外部类GreenhouseControls,所以内部类可以毫无阻碍地会见那些字段。另外,大大都action()要领也涉及到某些形式的硬件节制,这凡是都要求发出对非Java代码的挪用。
大大都Event类看起来都是相似的,但Bell(铃)和Restart(重启)属于非凡环境。Bell会发出响声,若尚未响铃足够的次数,它会在事件列内外添加一个新的Bell工具,所以今后会再度响铃。请留意内部类看起来为什么老是雷同于多重担任:Bell拥有Event的所有要领,并且也拥有外部类GreenhouseControls的所有要领。
Restart认真对系统举办初始化,所以会添加所有须要的事件。虽然,一种更机动的做法是制止举办“硬编码”,而是从一个文件里读入它们(第10章的一个操练会要求各人修改这个例子,从而到达这个方针)。由于Restart()仅仅是另一个Event工具,所以也可以在Restart.action()里添加一个Restart工具,使系统可以或许按期重启。在main()中,我们需要做的全部工作就是建设一个GreenhouseControls工具,并添加一个Restart工具,令其事情起来。
这个例子应该使各人对内部类的代价有一个越发深刻的认识,出格是在一个节制框架里利用它们的时候。另外,在第13章的后半部门,各人还会看到如何巧妙地操作内部类描写一个图形用户界面的行为。完成哪里的进修后,对内部类的认识将上升到一个前所未有的新高度。