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

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

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

  1. //: Event.java
  2.  
  3. abstract public class Event {
  4. private long evtTime;
  5. public Event(long eventTime) {
  6. evtTime = eventTime;
  7. }
  8. public boolean ready() {
  9. return System.currentTimeMillis() >= evtTime;    //如果系统时间大于设定的时间,就触发事件
  10. }
  11. abstract public void action();
  12. abstract public String description();
  13. } ///:~

  希望Event(事件)运行的时候,构建器即简单地捕获时间。同时ready()告诉我们何时该运行它。当然,ready()也可以在一个衍生类中被覆盖,将事件建立在除时间以外的其他东西上。action()是事件就绪后需要调用的方法。
  下面这个文件包含了实际的控制框架,用于管理和触发事件。第一个类它的职责是容纳Event对象。可用集合替换它。

  1. //: Controller.java
  2.  
  3. class EventSet {
  4. private Event[] events = new Event[100];
  5. private int index = 0;
  6. private int next = 0;
  7. public void add(Event e) {
  8. if(index >= events.length)
  9. return;
  10. events[index++] = e;
  11. }
  12. public Event getNext() {
  13. boolean looped = false;
  14. int start = next;
  15. do {
  16. next = (next + 1) % events.length;
  17. if(start == next) looped = true;
  18. if((next == (start + 1) % events.length)
  19. && looped)
  20. return null;
  21. } while(events[next] == null);
  22. return events[next];
  23. }
  24. public void removeCurrent() {
  25. events[next] = null;
  26. }
  27. }
  28.  
  29. public class Controller {
  30. private EventSet es = new EventSet();
  31. public void addEvent(Event c) { es.add(c); }
  32. public void run() {
  33. Event e;
  34. while((e = es.getNext()) != null) {
  35. if(e.ready()) {
  36. e.action();
  37. System.out.println(e.description());
  38. es.removeCurrent();
  39. }
  40. }
  41. }
  42. } ///:~

  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继承的:

  1. //: GreenhouseControls.java
  2.  
  3. public class GreenhouseControls
  4. extends Controller {
  5. private boolean light = false;
  6. private boolean water = false;
  7. private String thermostat = "Day";
  8. private class LightOn extends Event {
  9. public LightOn(long eventTime) {
  10. super(eventTime);
  11. }
  12. public void action() {
  13. light = true;
  14. }
  15. public String description() {
  16. return "Light is on";
  17. }
  18. }
  19. private class LightOff extends Event {
  20. public LightOff(long eventTime) {
  21. super(eventTime);
  22. }
  23. public void action() {
  24. light = false;
  25. }
  26. public String description() {
  27. return "Light is off";
  28. }
  29. }
  30. private class WaterOn extends Event {
  31. public WaterOn(long eventTime) {
  32. super(eventTime);
  33. }
  34. public void action() {
  35. water = true;
  36. }
  37. public String description() {
  38. return "Greenhouse water is on";
  39. }
  40. }
  41. private class WaterOff extends Event {
  42. public WaterOff(long eventTime) {
  43. super(eventTime);
  44. }
  45. public void action() {
  46. water = false;
  47. }
  48. public String description() {
  49. return "Greenhouse water is off";
  50. }
  51. }
  52. private class ThermostatNight extends Event {
  53. public ThermostatNight(long eventTime) {
  54. super(eventTime);
  55. }
  56. public void action() {
  57. thermostat = "Night";
  58. }
  59. public String description() {
  60. return "Thermostat on night setting";
  61. }
  62. }
  63. private class ThermostatDay extends Event {
  64. public ThermostatDay(long eventTime) {
  65. super(eventTime);
  66. }
  67. public void action() {
  68. // Put hardware control code here
  69. thermostat = "Day";
  70. }
  71. public String description() {
  72. return "Thermostat on day setting";
  73. }
  74. }
  75. private int rings;
  76. private class Bell extends Event {
  77. public Bell(long eventTime) {
  78. super(eventTime);
  79. }
  80. public void action() {
  81. // Ring bell every 2 seconds, rings times:
  82. System.out.println("Bing!");
  83. if(--rings > 0)
  84. addEvent(new Bell(
  85. System.currentTimeMillis() + 2000));
  86. }
  87. public String description() {
  88. return "Ring bell";
  89. }
  90. }
  91. private class Restart extends Event {
  92. public Restart(long eventTime) {
  93. super(eventTime);
  94. }
  95. public void action() {
  96. long tm = System.currentTimeMillis();
  97. // Instead of hard-wiring, you could parse
  98. // configuration information from a text
  99. // file here:
  100. rings = 5;
  101. addEvent(new ThermostatNight(tm));
  102. addEvent(new LightOn(tm + 1000));
  103. addEvent(new LightOff(tm + 2000));
  104. addEvent(new WaterOn(tm + 3000));
  105. addEvent(new WaterOff(tm + 8000));
  106. addEvent(new Bell(tm + 9000));
  107. addEvent(new ThermostatDay(tm + 10000));
  108. // Can even add a Restart object!
  109. addEvent(new Restart(tm + 20000));
  110. }
  111. public String description() {
  112. return "Restarting system";
  113. }
  114. }
  115. public static void main(String[] args) {
  116. GreenhouseControls gc = new GreenhouseControls();
  117. long tm = System.currentTimeMillis();
  118. gc.addEvent(gc.new Restart(tm));
  119. gc.run();
  120. }
  121. } ///:~

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

JAVA 内部类 (三)实例的更多相关文章

  1. Java并发(三):实例引出并发应用场景

    前两篇介绍了一些Java并发的基础知识,博主正巧遇到一种需求:查询数据库,根据查询结果集修改数据库记录,但整个流程是做成了一个schedule的,并且查询比较耗时,并且需要每两分钟执行一次,cpu经常 ...

  2. 内部类访问外部类的变量必须是final吗,java静态方法中不能引用非静态变量,静态方法中不能创建内部类的实例

    内部类访问外部类的变量必须是final吗? 如下: package com.java.concurrent; class A { int i = 3; public void shout() { cl ...

  3. java多线程三之线程协作与通信实例

    多线程的难点主要就是多线程通信协作这一块了,前面笔记二中提到了常见的同步方法,这里主要是进行实例学习了,今天总结了一下3个实例: 1.银行存款与提款多线程实现,使用Lock锁和条件Condition. ...

  4. Java通过继承外部类来建立该外部类的protected内部类的实例(转)

    原文链接:http://blog.sina.com.cn/s/blog_7de00ff60102xffx.html 如果想要在外部类的导出类(子类)中建立该外部类的为protected权限的内部类的实 ...

  5. 黑马----JAVA内部类

    黑马程序员:Java培训.Android培训.iOS培训..Net培训 黑马程序员--JAVA内部类 一.内部类分为显式内部类和匿名内部类. 二.显式内部类 1.即显式声明的内部类,它有类名. 2.显 ...

  6. java 内部类 *** 最爱那水货

    注: 转载于http://blog.csdn.net/jiangxinyu/article/details/8177326 Java语言允许在类中再定义类,这种在其它类内部定义的类就叫内部类.内部类又 ...

  7. java内部类实现多继承

    class Example1 { public String name() { return "liutao"; } } class Example2 { public int a ...

  8. js闭包vs Java内部类

    前言: 昨天写了一个关于Java内部的博客,在内部类的最后一点中谈到了Java闭包的概念,他是这样定义闭包的:闭包是一个可调用的对象,它记录了一些信息,这些信息来自创建它的作用域.结合Java的内部类 ...

  9. Java关键字(三)——static

    我们说Java是一种面向对象编程的语言,而对象是把数据及对数据的操作方法放在一起,作为一个相互依存的整体,对同类对象抽象出其共性,便是Java中的类,我们可以用类描述世间万物,也可以说万物皆对象.但是 ...

  10. java内部类 和外部类的区别

    java 内部类和静态内部类的区别  详细连接https://www.cnblogs.com/aademeng/articles/6192954.html 下面说一说内部类(Inner Class)和 ...

随机推荐

  1. 怎样用命令行管理SharePoint Feature?

    普通情况下对IT管理者来说.在SharePoint Farm中维护Feature,更喜欢使用命令行实现,这样能够省去登录到详细网站的操作. 比方IT接到end user的一个需求,要开启Site Co ...

  2. solaris软件管理 FTP

    安装一些常用软件 一.应用程序与系统命令的关系: 系统命令文件位置在 /bin /sbin下面或为shell内部指令:完成对系统的基本管理工作:一般在字符操作界面中运行:一般包括命令字.命令选项和命令 ...

  3. 命令行查看w3wp进程信息

    用windbg.mdbg等调试器调试时,当出现多个w3wp进程并且用户名相同,需要区分每个w3wp进程对应的PID(进程ID)和应用程序池信息. 我们用以下方式得到每个w3wp进程的详细信息. Win ...

  4. C# Select SelectMany 区别

    string[] text = { "Today is 2018-06-06", "weather is sunny", "I am happy&qu ...

  5. 笔记08 throw e 和throw 的区别

    throw e对原异常进行拷贝,然后将新的异常对象抛出,这一步拷贝就有可能导致错误啦,拷贝出来的异常对象可能和原来的异常对象不是一个类型. 比如原来的对象是个子类的异常对象,catch里声明的是父类异 ...

  6. 【重磅干货】看了此文,Oracle SQL优化文章不必再看!

    目录 SQL优化的本质 SQL优化Road Map 2.1 制定SQL优化目标 2.2 检查执行计划 2.3 检查统计信息 2.4 检查高效访问结构 2.5 检查影响优化器的参数 2.6 SQL语句编 ...

  7. java 多线程2(转载)

    http://www.cnblogs.com/DreamSea/archive/2012/01/11/JavaThread.html Ø线程的概述(Introduction) 线程是一个程序的多个执行 ...

  8. 代写GIS系统

    代写GIS系统,熟悉arcgis,leaflet ,百度地图等api.可以提供系统代写,技术咨询等

  9. 使用Entity Framework和WCF Ria Services开发SilverLight之6:查找指定字段

    对数据库表指定字段的查找,又是实际工作中的一项必要工作.SL客户端仅获取实际需要的指定的字段,好处很多,比如:有助于减少网络流量. 有两类这样的使用场景. 1:联表查询不需要外键表 在上一篇中,我们使 ...

  10. Git Xcode配置

    本文转载至 http://www.cnblogs.com/imzzk/p/xcode_git.html 感谢作者分享 Git源代码管理工具的出现,使得我们开发人员对于源码的管理更加方便快捷.至于Git ...