内部类与控制框架

在我将要介绍的控制框架(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();

  }

}   ///:~


这个类的作用是初始化系统,所以它添加了所有相应的事件。当然,更灵活的方法是避免

对事件进行硬编码,取而代之的是从文件中读取需要的事件。(第十二章的练习会要求你

照此方法修改这个例子。)如果你提供了命令行参数,系统会以它作为毫秒数,决定什么

时候终止程序(这是测试程序时使用的)。

 

这个例子应该能使你更了解内部类的价值,特别是在控制框架中使用内部类的时候。而在

第十四章中,你将看到内部类如何优雅地描述图形用户界面的行为。到那时,你应该就完

全信服内部类的价值了。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值