design patterns


Managing the Future

Recall the example of hotel design given in the first chapter. With inheritance and interfaces you can build a lot of complex cross-class structure, but that isn't enough if the problem you're trying to solve is sufficiently interesting. For example, a Guest has accidentally started a fire and calls down to the FrontDesk to report the problem. The FrontDesk has standing instructions to call the Manager in case of fire. Now suppose that during design you had decided that every time there is a potential for fire, the relevant class has to call the Manager class' handleFire() method. Since nearly any staff person object could notice a fire, nearly every staff person class would call Manager in case of fire.

This works fine until the government changes the fire safety laws, or hotel management changes the fire-reporting protocol. If you now have to report every fire to a new class, say FireDepartment, you'll have to change all those calls to Manager in every single one of perhaps thousands of classes. If you miss one, you have a bug (and perhaps a burnt-out hotel and a lawsuit). This is how spacecraft crash, and how large corporations misplace vast sums. If you had thought ahead during design, though, you could instead have had all classes pass on any fire problem to some intermediate class, say FireMarshal, which then calls Manager. Although this seems pointless as well as inefficient, if you later need to call some other class to handle all fires, you only have to change one class, FireMarshal.

As a general rule, the fewer unrelated things any class has to care about, the more limited any change will need to be, and thus the more resilient your design will be in the face of future changes. Suppose, for example, you later discover that you have to sometimes call the fire department (a kitchen fire), sometimes call the manager (a fire in a dumpster), and sometimes call the electrician (an electrical fire), you can put all the logic about what to do with various types of fires in the FireMarshal class; no other class has to care how the fires they report are actually handled. This is a simple design pattern, a general way to isolate stress points in your design so that future change is easier---and given the complexity and economic importance of problems that information architects are asked to solve every day, flexibility in the face of change is crucial.

Finally, no matter how sophisticated and flexible your original design is, if it's large enough and your implemented system exists for long enough, it will, inevitably, develop scars and warts and smelly bits as people unfamiliar with your original elegant design change the system to accomodate new behavior. The old engineering saying, "If it ain't broke, don't fix it", is true of hardware, but is less true of software because we often ask software to do many new things long after its initial creation. So even if your system isn't broken, if you expect it to be around for a long time then if it's baroque, you should fix it. Take the guest check-in example discussed before. If the spectrum of hotel guests changes radically so that over time many guests need ever more involved procedures to verify their credit worthiness, you may decide to modify the check-in procedure to make future change easier. Perhaps you decide to invent a new role, CreditCheckAgent, who the Deskclerk, Manager, and Accountant will delegate to on guest check-in. Rather than spreading credit-checking knowledge around, it alone would know about how to check the hotel's BankAccount and PastGuestList, and so on. Changing your system, however, needs to be done carefully since several methods in otherwise unrelated classes are intricately connected to the check-in procedure---which is what led to the problem in the first place. If you try to accomplish the entire change all at once you are likely to forget something somewhere. It's better to modify your system a tiny piece at a time while maintaining all the rest of the original structure until you arrive at a new structure.

This evolutionary process is called refactoring since the task is to pick out some inflexible subsystem and slowly move around little bits of responsiblity to other, possibly even new, roles, until the inflexibility goes away. To refactor your check-in subsystem, you might, for example, first create an empty CreditCheckAgent role, test that everything is still okay, delegate credit card acceptance from Deskclerk to it, test that everything is still okay, delegate past guest list checking from Manager to it, test, delegate hotel bank account checking from Accountant to it, test, and so on. Each change is tiny, so finding errors as you go is easier than if you'd made one huge change all at once, so it's easy to check that the entire system still functions well after each change until eventually you end up with a new system.

Hide Your Data

Establish Firm Contracts

Protect Your Constants

Control Your Own State

Delegate Responsibilities

Use Generic Interfaces

Delegate Control

patterns: Singleton, Observer, Factory, Decorator, Adapter

animation applets need at least two things: double-buffering and a thread. over and over again i'd write the same bits of code and only vary the animation code. eventually even i realized that this was stupid. what i needed was an observer.

the observer would be the animation code and it would observe a clock. the clock's sole purpose would be to regularly execute the tick() method of the animation code.

further, i realized that i could subtract the double-buffering out too. that went into a generic AnimationShell applet that my animation applets now subclass.

consequently, i can write a complex animation applet in less than a third the time it used to take me---and i can't make mistakes in the double-buffering or thread portions since they're already done and debugged.

what was once 1 class now becomes 4 classes (the interface, the two helper classes, plus the guts of the animation code (not included below since it varies from application to application). but in exchange i've gained (1) robustness (this code is much easier to modify arbitrarily) (2) cleanliness (there is a clean separation of responsibilities amongst all the classes, each one has its job to do and ONLY that job) and (3) understandability (it should be easy for anyone to figure out what i'm doing once they know about the observer pattern).
contents of file ClockObserver.java:


interface ClockObserver
   {
   /*
   ClockObservers must have a tick() method that the clock they're
   observing will ask them to execute on each clock tick.
   */

   public void tick();
   }

contents of file Clock.java:

import java.lang.Runnable;
import java.lang.Thread;
import java.util.Vector;
import java.util.Enumeration;

class Clock
   implements Runnable
   {
   /*
   Define an object that regularly executes the tick() method of all
   its observers.

   Objects become observers by executing this clock's addClockObserver()
   method or by passing themselves to this clock on its creation.

   Public Constructors & Methods:
   ------------------------------
   Clock()
   Clock(ticksPerSecond)
   Clock(ticksPerSecond, observer)
   addClockObserver(observer)
   deleteClockObserver(observer)
   start()
   stop()
   run()
   timeSinceLastTick()
   */

   //my thread, observer list, clock rate, and temporary variables
   protected Thread thread;
   protected Vector observers = new Vector();
   protected int ticksPerSecond = 25;
   protected long sleepTime = 40;
   protected long startTickTime= 0, currentTickTime= 0, lastTickTime= 0;
   protected long tickCount = 0;

   ///////////////////////////////////////////////////////////
   public Clock()
      {
      /*
      Create a clock with a default speed and no observers.
      */

      thread = new Thread(this);
      }

   ///////////////////////////////////////////////////////////
   public Clock(int ticksPerSecond)
      {
      /*
      Create a clock with a given speed and no observers.
      */

      thread = new Thread(this);
      this.ticksPerSecond = ticksPerSecond;
      //convert to milliseconds
      sleepTime = (long) (1000 / ticksPerSecond);
      }

   ///////////////////////////////////////////////////////////
   public Clock(int ticksPerSecond, ClockObserver observer)
      {
      /*
      Create a clock with a given speed and a given observer
      and start it.
      */

      thread = new Thread(this);
      this.ticksPerSecond = ticksPerSecond;
      //convert to milliseconds
      sleepTime = (long) (1000 / ticksPerSecond);
      addClockObserver(observer);
      this.start();
      }

   ///////////////////////////////////////////////////////////
   public synchronized void addClockObserver(ClockObserver observer)
      {
      observers.addElement(observer);
      }

   ///////////////////////////////////////////////////////////
   public synchronized void deleteClockObserver(ClockObserver observer)
      {
      observers.removeElement(observer);
      }

   ///////////////////////////////////////////////////////////
   public synchronized void die(ClockObserver observer)
      {
      /*
      Die if requested to by my last observer.

      This would leave the observer (and any careless past observers)
      with a null pointer. how to fix that without requiring them to remember
      to null out their references to me? need some way to make myself private
      while still allowing arbitrary objects to observe me. I need a
      ClockManager.
      */

      if ((observers.size() == 1) && observers.elementAt(0).equals(observer))
         {
         this.stop();
         observers = null;
         try {this.finalize();}
         catch (Throwable ignored) {} //just die
         }
      }

   ///////////////////////////////////////////////////////////
   public void start()
      {
      startTickTime = System.currentTimeMillis();
      lastTickTime = startTickTime;
      currentTickTime = startTickTime;

      if (thread == null)
         thread = new Thread(this);
      thread.start();
      }

   ///////////////////////////////////////////////////////////
   public void stop()
      {
      thread = null;
      }

   ///////////////////////////////////////////////////////////
   public void run()
      {
      while (thread != null)
         {
         currentTickTime = System.currentTimeMillis();

         //execute all my observers' tick() methods
         Enumeration watchers = observers.elements();
         while (watchers.hasMoreElements())
            ((ClockObserver) watchers.nextElement()).tick();
         tickCount++;

         long elapsedTime = System.currentTimeMillis() - currentTickTime;

         lastTickTime = currentTickTime;

         //the time to sleep depends on how much time it took to execute all
         //the tick() methods, so sleep only long enough to keep the beat
         if (elapsedTime < sleepTime)
            {
            try {Thread.sleep(sleepTime - elapsedTime);}
            catch (InterruptedException exception)
               {
               System.out.println("Clock: something bad happened.");
               exception.printStackTrace();
               throw new RuntimeException(exception.toString());
               }
            }
         }
      }

   ///////////////////////////////////////////////////////////
   public long timeSinceLastTick()
      {
      return currentTickTime - lastTickTime;
      }

   ///////////////////////////////////////////////////////////
   protected long getTickCount()
      {
      return tickCount;
      }
   }

contents of file AnimationShell.java:

import ClockObserver;
import Clock;

import java.applet.Applet;
import java.awt.Image;
import java.awt.Graphics;
import java.awt.Color;

public class AnimationShell extends Applet
   implements ClockObserver
   {
   /*
   This applet shell can be subclassed to produce animation applets.
   It uses an offscreen image to double buffer the animation and so
   avoid image flicker and it observes a clock that executes its tick()
   method to control when the animation is redrawn. It also lets subclasses
   control how the animation is drawn (whether it's to be paused, resumed,
   restarted, or killed).

   Public Methods:
   ---------------
   init()
   start()
   stop()
   run()
   update(graphicsContext)
   paint(graphicsContext)
   tick()
   pauseAnimation()
   resumeAnimation()
   restartAnimation()
   endAnimation()
   */

   //my offscreen bitmap and its graphics context
   protected Image offscreenImage;
   protected Graphics offscreenGraphics;

   //my clock and its speed
   Clock clock;
   protected int CLOCK_TICKS_PER_SECOND = 25;

   //my window's background color and dimensions
   protected Color backgroundColor = Color.black;
   protected int windowWidth, windowHeight;

   //the animation controller
   protected boolean paused;

   ///////////////////////////////////////////////////////////
   public void init()
      {
      /*
      Setup my onscreen and offscreen bitmaps.
      */

      //fetch my window's dimensions and blank the screen
      windowWidth = this.getSize().width;
      windowHeight = this.getSize().height;
      this.setBackground(backgroundColor);

      //setup my offscreen bitmap
      offscreenImage = this.createImage(windowWidth, windowHeight);
      offscreenGraphics = offscreenImage.getGraphics();
      offscreenGraphics.setColor(backgroundColor);
      offscreenGraphics.fillRect(0, 0, windowWidth, windowHeight);

      //start my clock
      clock = new Clock(CLOCK_TICKS_PER_SECOND, this);

      //start the animation
      this.start();
      }

   ///////////////////////////////////////////////////////////
   public void start()
      {
      /*
      Start the animation.
      */

      paused = false;
      }

   ///////////////////////////////////////////////////////////
   public void stop()
      {
      /*
      Stop the animation.
      */

      paused = true;
      }

   ///////////////////////////////////////////////////////////
   public void tick()
      {
      /*
      This method is regularly executed by my clock.
      */

      if (! paused)
         run();
      }

   ///////////////////////////////////////////////////////////
   public void run()
      {
      /*
      Generate the next animation frame and paint it.
      */

      updateAnimation();
      this.repaint();
      }

   ///////////////////////////////////////////////////////////
   public void pauseAnimation()
      {
      this.stop();
      }

   ///////////////////////////////////////////////////////////
   public void resumeAnimation()
      {
      this.start();
      }

   ///////////////////////////////////////////////////////////
   public void restartAnimation()
      {
      /*
      Start the animation over from the beginning.
      */

      this.stop();
      this.init();
      }

   ///////////////////////////////////////////////////////////
   public void endAnimation()
      {
      /*
      Kill the animation dead.
      */

      this.stop();
      offscreenImage = null;
      offscreenGraphics = null;
      clock.die(this);
      clock = null;
      }

   ///////////////////////////////////////////////////////////
   public void update(Graphics graphicsContext)
      {
      /*
      Overridde the default Applet.update() to decrease flicker.
      */

      paint(graphicsContext);
      }

   ///////////////////////////////////////////////////////////
   public void paint(Graphics graphicsContext)
      {
      /*
      Paint my offscreen bitmap to my window.
      */

      //blank my offscreen bitmap
      offscreenGraphics.setColor(backgroundColor);
      offscreenGraphics.fillRect(0, 0, windowWidth, windowHeight);

      //paint the current animation frame into it
      paintAnimationFrame(offscreenGraphics);

      //draw it to the screen
      graphicsContext.drawImage(offscreenImage, 0, 0, this);
      }

   ///////////////////////////////////////////////////////////
   protected void updateAnimation()
      {
      /*
      Update the animation that i will later paint to my offscreen bitmap.

      This method does nothing here; it's to be overridden by subclasses.
      */
      }

   ///////////////////////////////////////////////////////////
   protected void paintAnimationFrame(Graphics graphicsContext)
      {
      /*
      Paint a frame of the animation into graphicsContext.

      This method does nothing here; it's to be overridden by subclasses.
      */
      }
   }


last | | contents | | next