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

到目前为止,大家已接触了对内部类的运作进行描述的大量语法与概念。但这些并不能真正说明内部类存在的原因。为什么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();
  }
} ///:~

注意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章的后半部分,大家还会看到如何巧妙地利用内部类描述一个图形用户界面的行为。完成那里的学习后,对内部类的认识将上升到一个前所未有的新高度。

以上是小编为您精心准备的的内容,在的博客、问答、公众号、人物、课程等栏目也有的相关内容,欢迎继续使用右上角搜索按钮进行搜索框架
, private
, event
, public
, 控制
, 一个
addevent
,以便于您获取更多的相关知识。

时间: 2024-10-05 15:40:52

为什么要用内部类:控制框架的相关文章

如何控制框架页的滚动

控制 如何控制框架页的滚动 解决思路:     利用框架文档中window对象的scrollBy方法来滚动. 具体步骤: 1. 包含框架页的代码. <script> var itv,stepX,stepY,obj function scrollStart(x,y){ stepX=x       //X轴方向上的偏移量 stepY=y       //Y轴方向上的偏移量 obj=document.frames.demo    //捕获框架对象 //设置间隔事件,每10毫秒以stepX和stepY

如何控制框架页内中链接的目标

控制|链接 要在一个框架中使用链接以打开另一个框架中的文档,您必须设置链接目标.链接的 target 属性指定在其中打开链接的内容的框架或窗口. 例如,如果您的导航条位于左框架,并且您希望链接的材料显示在右侧的主要内容框架中,则您必须将主要内容框架的名称指定为每个导航条链接的目标.当访问者单击导航链接时,将在主框架中打开指定的内容. 若要设置目标框架,请执行以下操作: 在"设计"视图中,选择文本或对象. 在属性检查器("窗口">"属性")的

框架页面尽可以这么用(后置代码中控制框架)

下面是框架页: <%@ Page CodeBehind="Frameset.aspx.cs" Language="c#" AutoEventWireup="false" Inherits="IbatisTest.Web.Frameset" %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN"> <HTML&g

js控制框架页面表单

function setobj(obj) { for (i=1; i<parent.chatform.document.forms[0].object.length; i++) {  var iscontinue=true;  if (parent.chatform.document.forms[0].object.options[i].value==obj)  {   parent.chatform.document.forms[0].object.selectedIndex = i;   i

goRBAC —— Go 语言基于角色的权限控制框架

goRBAC 为 Go 语言应用提供了轻量级的基于角色的访问控制. 该包适用于: * 实体具有一个或多个角色 * 角色需要分配权限 * 权限需要分配给角色 因此,RBAC 具有以下模型: * 在实体和角色之间具有多对多关系 * 在角色和权限之间具有多对多关系 * 角色可以具有父角色(权限继承) 示例代码: import github.com/mikespook/gorbac rbac := gorbac.New() rbac := gorbac.NewWithFactory(YourOwnFac

在Dreamweaver中巧用框架建立网站

dreamweaver 本文选自4u2v工作室编写的<Dreamweaver网页设计与制作100例>(人民邮电出版社出版,ISBN: 7115142394).未经著作权所有者书面授权许可,禁止转载本文. 购买地址:点击访问 有时看到一些论坛,左边是每个讨论区的名称,点击任意一个讨论区就可以在右部区域中看见相应讨论区的内容,不过左右部分是独立显示的,比如拖动左边的滚动条不会影响右侧的显示效果.其实这是在页面中利用了框架技术,因此可以把浏览器的显示空间分割为几个部分,每个部分都独立显示网页内容.而

Dreamweaver如何用框架建立网站

有时看到一些论坛,左边是每个讨论区的名称,点击任意一个讨论区就可以在右部区域中看见相应讨论区的内容,不过左右部分是独立显示的,比如拖动左边的滚动条不会影响右侧的显示效果.其实这是在页面中利用了框架技术,因此可以把浏览器的显示空间分割为几个部分,每个部分都独立显示网页内容.而且把几个框架结合在一起构成框架集,能够让页面具有更为丰富的效果. 效果说明 先建立两个页面,分别为" myphoto.htm "." mydiary.htm ".前一个是相册页面,后一个是日记页面

为什么使用框架 使用框架的优缺点_相关技巧

正文开始 我们是由于效率和易用性的考虑才产生框架.框架能节省开发时间.框架强制使用公共的约定,因此它能有效地解决一些共有的问题,比如页面渲染,assert判断,安全或者应用配置等.这些共有的问题有个共通的特性是会在每个web应用上都用到. 框架是非常好的,它能让决定更连贯.框架能避免我们写一大堆自定义模块来实现这些性能,我们所需要做的就是将这些共用模块放在框架中实现.框架节省了我们不少的时间和精力,并且让扩展变得更容易.但是这也是问题的根本所在. 由于框架能在我们做代码决策的时候提供很多的帮助,

TIJ阅读笔记(第八章)

笔记 8:接口(interface)与内部类(inner class) 接口(interface)  可以把它想象成"纯的"abstract类.能让开发人员定义类的形式:方法名,参数列表,返回值的类型,但是却没有方法的正文.interface也可以包含数据成员,但是它天生就是static和final的.interface只提供形式,不谈实现.  interface会被用作定义类之间的"协议(protocol)".  当你implements一个interface的时