Follow Techotopia on Twitter

On-line Guides
All Guides
eBook Store
iOS / Android
Linux for Beginners
Office Productivity
Linux Installation
Linux Security
Linux Utilities
Linux Virtualization
Linux Kernel
System/Network Admin
Programming
Scripting Languages
Development Tools
Web Development
GUI Toolkits/Desktop
Databases
Mail Systems
openSolaris
Eclipse Documentation
Techotopia.com
Virtuatopia.com
Answertopia.com

How To Guides
Virtualization
General System Admin
Linux Security
Linux Filesystems
Web Servers
Graphics & Desktop
PC Hardware
Windows
Problem Solutions
Privacy Policy

  




 

 

Thinking in Java
Prev Contents / Index Next

Inner classes & control frameworks

A more concrete example of the use of inner classes can be found in something that I will refer to here as a control framework.

An application framework is a class or a set of classes that’s designed to solve a particular type of problem. To apply an application framework, you typically inherit from one or more classes and override some of the methods. The code that you write in the overridden methods customizes the general solution provided by that application framework in order to solve your specific problem (this is an example of the Template Method design pattern; see Thinking in Patterns (with Java) at www.BruceEckel.com). The control framework is a particular type of application framework dominated by the need to respond to events; a system that primarily responds to events is called an event-driven system. One of the most important problems in application programming is the graphical user interface (GUI), which is almost entirely event-driven. As you will see in Chapter 14, the Java Swing library is a control framework that elegantly solves the GUI problem and that heavily uses inner classes.

To see how inner classes allow the simple creation and use of control frameworks, consider a control framework whose job is to execute events whenever those events are “ready.” Although “ready” could mean anything, in this case the default will be based on clock time. What follows is a control framework that contains no specific information about what it’s controlling. That information is supplied during inheritance, when the “template method” is implemented.

First, here is the interface that describes any control event. It’s an abstract class instead of an actual interface because the default behavior is to perform the control based on time. Thus, some of the implementation is included here:

//: c08:controller:Event.java
// The common methods 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();
} ///:~


The constructor captures the time (from the time of creation of the object) when you want the Event to run and then calls start( ), which takes the current time and adds the delay time to produce the time when the event will occur. Rather than being included in the constructor, start( ) is a separate method because this way, it allows you to restart the timer after the event has run out so the Event object can be reused. For example, if you want a repeating event, you can simply call start( ) inside your action( ) method.

ready( ) tells you when it’s time to run the action( ) method. Of course, ready( ) could be overridden in a derived class to base the Event on something other than time.

The following file contains the actual control framework that manages and fires events. The Event objects are held inside a container object of type ArrayList, which you’ll learn more about in Chapter 11. For now, all you need to know is that add( ) will append an Object to the end of the ArrayList, size( ) produces the number of entries in the ArrayList, get( ) will fetch an element from the ArrayList at a particular index, and remove( ) removes an element from the ArrayList, given the element number you want to remove.

//: c08:controller:Controller.java
// With Event, the generic framework for control systems.
package c08.controller;
import java.util.*;

public class Controller {
  // An object from 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);
        }
      }
    }
  }
} ///:~


The run( ) method loops through eventList, hunting for an Event object that’s ready( ) to run. For each one it finds ready( ), it prints information using the object’s toString( ) method, calls the action( ) method, and then removes the Event from the list.

Note that so far in this design you know nothing about exactly what an Event does. And this is the crux of the design—how it “separates the things that change from the things that stay the same.” Or, to use my term, the “vector of change” is the different actions of the various kinds of Event objects, and you express different actions by creating different Event subclasses.

This is where inner classes come into play. They allow two things:

  1. To create the entire implementation of a control framework in a single class, thereby encapsulating everything that’s unique about that implementation. Inner classes are used to express the many different kinds of action( ) necessary to solve the problem.
  2. Inner classes keep this implementation from becoming awkward, since you’re able to easily access any of the members in the outer class. Without this ability the code might become unpleasant enough that you’d end up seeking an alternative. href="TIJ310_022.htm">[39] Each action is entirely different: turning lights, water, and thermostats on and off, ringing bells, and restarting the system. But the control framework is designed to easily isolate this different code. Inner classes allow you to have multiple derived versions of the same base class, Event, within a single class. For each type of action, you inherit a new Event inner class, and write the control code in the action( ) implementation.

    As is typical with an application framework, the class GreenhouseControls is inherited from Controller:

    //: c08: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.
    import com.bruceeckel.simpletest.*;
    import c08.controller.*;
    
    public class GreenhouseControls extends 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 hardware control code here to
          // physically turn 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 hardware control code here to
          // physically turn 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 hardware control 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 hardware control 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 hardware control 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 hardware control code here.
          thermostat = "Day";
        }
        public String toString() {
          return "Thermostat on day setting";
        }
      }
      // An example of an action() that inserts a
      // new one of itself 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";  }
      }
    } ///:~


    Note that light, water, and thermostat belong to the outer class GreenhouseControls, and yet the inner classes can access those fields without qualification or special permission. Also, most of the action( ) methods involve some sort of hardware control.

    Most of the Event classes look similar, but Bell and Restart are special. Bell rings and then adds a new Bell object to the event list, so it will ring again later. Notice how inner classes almost look like multiple inheritance: Bell and Restart have all the methods of Event and also appear to have all the methods of the outer class GreenhouseControls.

    Restart is given an array of Event objects that it adds to the controller. Since Restart( ) is just another Event object, you can also add a Restart object within Restart.action( ) so that the system regularly restarts itself.

    The following class configures the system by creating a GreenhouseControls object and adding various kinds of Event objects. This is an example of the Command design pattern:

    //: c08:GreenhouseController.java
    // Configure and execute the greenhouse system.
    // {Args: 5000}
    import c08.controller.*;
    
    public class GreenhouseController {
      public static void main(String[] args) {
        GreenhouseControls gc = new GreenhouseControls();
        // Instead of hard-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();
      }
    } ///:~


    This class initializes the system, so it adds all the appropriate events. Of course, a more flexible way to accomplish this is to avoid hard-coding the events and instead read them from a file. (An exercise in Chapter 12 asks you to modify this example to do just that.) If you provide a command-line argument, it uses this to terminate the program after that many milliseconds (this is used for testing).

    This example should move you toward an appreciation of the value of inner classes, especially when used within a control framework. However, in Chapter 14 you’ll see how elegantly inner classes are used to describe the actions of a graphical user interface. By the time you finish that chapter, you should be fully convinced.
    Thinking in Java
    Prev Contents / Index Next


 
 
   Reproduced courtesy of Bruce Eckel, MindView, Inc. Design by Interspire