为什么要用内部类:控制框架

  一个“应用程序框架”是指一个或一系列类,它们专门设计用来解决特定类型的问题。为应用应用程序框架,我们可从一个或多个类继承,并覆盖其中的部分方法。我们在覆盖方法中编写的代码用于定制由那些应用程序框架提供的常规方案,以便解决自己的实际问题。“控制框架”属于应用程序框架的一种特殊类型,受到对事件响应的需要的支配;主要用来响应事件的一个系统叫作“由事件驱动的系统”。在应用程序设计语言中,最重要的问题之一便是“图形用户界面”(GUI),它几乎完全是由事件驱动的。正如大家会在第13章学习的那样,Java 1.1 AWT属于一种控制框架,它通过内部类完美地解决了GUI的问题。
  为理解内部类如何简化控制框架的创建与使用,可认为一个控制框架的工作就是在事件“就绪”以后执行它们。尽管“就绪”的意思很多,但在目前这种情况下,我们却是以“系统时间”为基础。

  随后,请认识到针对控制框架需要控制的东西,框架内并未包含任何特定的信息。首先,它是一个特殊的接口,描述了所有控制事件。它可以是一个抽象类,而非一个实际的接口。由于默认行为是根据时间控制的,所以部分实施细节可能包括:

//: Event.java

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()是事件就绪后需要调用的方法。
  下面这个文件包含了实际的控制框架,用于管理和触发事件。第一个类它的职责是容纳Event对象。可用集合替换它。

//: Controller.java

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;
events[index++] = e;
}
public Event getNext() {
boolean looped = false;
int start = next;
do {
next = (next + 1) % events.length;
if(start == next) looped = true;
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个事件。index(索引)在这里用于跟踪下一个可用的空间,而next(下一个)帮助我们寻找列表中的下一个事件,了解自己是否已经循环到头。在对getNext()的调用中,这一点是至关重要的,因为一旦运行,Event对象就会从列表中删去(使用removeCurrent())。所以getNext()会在列表中向前移动时遇到“空洞”。
  注意removeCurrent()并不只是指示一些标志,指出对象不再使用。相反,它将句柄设为null。这一点是非常重要的,因为假如垃圾收集器发现一个句柄仍在使用,就不会清除对象。若认为自己的句柄可能象现在这样被挂起,那么最好将其设为null,使垃圾收集器能够正常地清除它们。
  Controller是进行实际工作的地方。它用一个EventSet容纳自己的Event对象,而且addEvent()允许我们向这个列表加入新事件。但最重要的方法是run()。该方法会在EventSet中遍历,搜索一个准备运行的Event对象——ready()。对于它发现ready()的每一个对象,都会调用action()方法,打印出description(),然后将事件从列表中删去。
  注意在迄今为止的所有设计中,我们仍然不能准确地知道一个“事件”要做什么。这正是整个设计的关键;它怎样“将发生变化的东西同没有变化的东西区分开”?或者用我的话来讲,“改变的意图”造成了各类Event对象的不同行动。我们通过创建不同的Event子类,从而表达出不同的行动。
  这里正是内部类大显身手的地方。它们允许我们做两件事情:
  (1)
在单独一个内部类里表达一个控制框架应用的全部实施细节,从而完整地封装与那个实施有关的所有东西。内部类用于表达多种不同类型的action(),它们用于解决实际的问题。而对于外部则完全是屏蔽的。
  (2)
内部类使我们具体的实施变得更加巧妙,因为能方便地访问外部类的任何成员。若不具备这种能力,代码看起来就可能没那么使人舒服,最后不得不寻找其他方法解决。

  现在要请大家思考控制框架的一种具体实施方式,它设计用来控制温室(Greenhouse)功能)。每个行动都是完全不同的:控制灯光、供水以及温度自动调节的开与关,控制响铃,以及重新启动系统。但控制框架的设计宗旨是将不同的代码方便地隔离开。对每种类型的行动,都要继承一个新的Event内部类,并在action()内编写相应的控制代码。

作为应用程序框架的一种典型行为,GreenhouseControls类是从Controller继承的:

//: GreenhouseControls.java

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() {
light = true;
}
public String description() {
return "Light is on";
}
}
private class LightOff extends Event {
public LightOff(long eventTime) {
super(eventTime);
}
public void action() {
light = false;
}
public String description() {
return "Light is off";
}
}
private class WaterOn extends Event {
public WaterOn(long eventTime) {
super(eventTime);
}
public void action() {
water = true;
}
public String description() {
return "Greenhouse water is on";
}
}
private class WaterOff extends Event {
public WaterOff(long eventTime) {
super(eventTime);
}
public void action() {
water = false;
}
public String description() {
return "Greenhouse water is off";
}
}
private class ThermostatNight extends Event {
public ThermostatNight(long eventTime) {
super(eventTime);
}
public void action() {
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";
}
}
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();
}
} ///:~

  注意light(灯光)、water(供水)、thermostat(调温)以及rings都隶属于外部类GreenhouseControls,所以内部类可以毫无阻碍地访问那些字段。
  大多数Event类看起来都是相似的,但Bell(铃)和Restart(重启)属于特殊情况。Bell会发出响声,若尚未响铃足够的次数,它会在事件列表里添加一个新的Bell对象,所以以后会再度响铃。请注意内部类看起来为什么总是类似于多重继承:Bell拥有Event的所有方法,而且也拥有外部类GreenhouseControls的所有方法。
  Restart负责对系统进行初始化,所以会添加所有必要的事件。当然,一种更灵活的做法是避免进行“硬编码”,而是从一个文件里读入它们(第10章的一个练习会要求大家修改这个例子,从而达到这个目标)。由于Restart()仅仅是另一个Event对象,所以也可以在Restart.action()里添加一个Restart对象,使系统能够定期重启。在main()中,我们需要做的全部事情就是创建一个GreenhouseControls对象,并添加一个Restart对象,令其工作起来。
这个例子应该使大家对内部类的价值有一个更加深刻的认识,特别是在一个控制框架里使用它们的时候。此外对于SWING,大家还会看到如何巧妙地利用内部类描述一个图形用户界面的行为。完成那里的学习后,对内部类的认识将上升到一个前所未有的新高度。

最新文章

  1. 最新的 cocoaPods 安装方法
  2. redis sentinel基本命令与参数
  3. mysql 语法总结
  4. PHP MYSQLI中事务处理
  5. jquery M97-datepicker日历控件
  6. HDOJ(HDU) 2135 Rolling table
  7. android Animation动画的xml使用
  8. android visible invisible和gone差异
  9. vultr vps2016年免费升级流量和cpu
  10. webx request注入单例增强实现
  11. 关于Javascript循环体变量声明与初始化的效率问题
  12. mybatis学习一
  13. day 05字典相关内容
  14. parseFloat()为什么没有效果
  15. Python自定义-分页器
  16. WEBBASE篇: 第七篇, JavaScript知识1
  17. 转:el表达式获取map对象的内容 & js中使用el表达式 & js 中使用jstl 实现 session.removeattribute
  18. React Native原生模块向JS传递数据的几种方式(Android)
  19. linux下设置mysql表名不区分大小写
  20. php获取全选checkbox多个值

热门文章

  1. ActiveX控件打包成Cab置于网页中自动下载安装
  2. mysql drop table命令使用总结
  3. jsp表达式
  4. smali语法(一)
  5. ios开发动物园管理 继承多态的实现
  6. Drupal 初次使用感受,兴许补充。
  7. c#生成试卷。。。
  8. meteor---在合并打包多个文件ZIP下载的功能
  9. 【LeetCode】Rotate List
  10. STM32 ~ ili9341 横屏驱动代码