10.6 Procedural Reuse in Java

 < Day Day Up > 



10.6 Procedural Reuse in Java

Method reuse is achieved by taking a number of procedural statements and putting them into a method that can be called from a number of places in a program. Exhibit 2 (Program10.2) is an example of method reuse to implement the move method to solve the moving of the concurrent balls problems in Exhibit 1 (Program10.1). A move method is defined that does the correct locking on the object, thus removing the need for synchronized and try blocks inside of the run method. This cuts down a lot on the amount of code, making it more readable. It also abstracts out the coordination of the two threads so the code is better cataloged and thus more usable. Finally, it also implements the code in one place so that any bugs or optimizations can be addressed in a single method in the program.

Exhibit 2: Program10.2: Method Reuse

start example

 import java.awt.*; import java.util.*; import animator.*; public class ConcurrentBall implements DrawListener, Runnable {   private Path myPath;   private Animator animator;   /** Constructor. Note that we need to register with the    *  animator through the run method.    */   public ConcurrentBall(Animator animator) {     this.animator = animator;   }   /**    *  The run method simulates an asynchronous ball. The    *  myPath variable is set here and used in the draw method    *  and is intended to coordinate the ball thread running in    *  this method and the GUI thread (from the animator)    *  running in the draw method. This works correctly.    */   public void run() {     // Set an initial point to draw this object, and then add it     // to the animator.     myPath = new StraightLinePath(10, 205, 10, 205, 1);     animator.addDrawListener(this);     Random random = new Random(System.currentTimeMillis());     try {       while(true) {        move(new StraightLinePath(410, 205, 10, 205, 50));        Thread.sleep(random.nextInt(10000));        move(new StraightLinePath(10, 205, 410, 205, 50));        Thread.sleep(random.nextInt(10000));       }     } catch (InterruptedException e) {     }   } /**   * This method allows a thread to move to completion before   * it continues. A Path variable is defined and then a wait is   * done. This wait is matched by a notify in the draw method   * when this thread has reached the end of the path,   * releasing the thread to continue running.   */  public synchronized void move(Path path) {    myPath = path;    try {      wait();    } catch (InterruptedException e) {    }  }   /**    *  Draw is called each time through the animator loop to draw    *  the object. It simply uses the path to calculate the    *  position of this object and then draws itself at that    *  position. When the end of the path is reached, it notifies    *  the ball thread.    */   public synchronized void draw(DrawEvent de) {     Point p = myPath.nextPosition();     Graphics g = de.getGraphics();     g.setColor(Color.red);     g.fillOval((int)p.getX(), (int)p.getY(), 15, 15);     if (! myPath.hasMoreSteps())       notify();   }   /**    * The main method just creates the animator    * and the ball threads and starts them running.    */   public static void main(String args[]) {    Animator animator = new Animator();    ConcurrentBall cb1 = new ConcurrentBall(animator);    (new Thread(cb1)).start();    animator.setVisible(true);   } } 

end example

Method reuse allows sets of procedural statements to be abstracted, implemented as methods, and reused. By changing the parameters to the methods, the actual behavior can be modified to fit specific programming problems. In the case of the move method, the path that is used can be changed. The Java API has numerous examples of procedural reuse; for example, Integer.parseInt would be called to parse a String to get an int, and Math.random would be called for a random number. What these methods have in common is that they are static methods in a class. They do not rely on an object, and in fact do not use any data associated with an object. They simply perform a procedural service in the program. Because these procedural methods do not use any data from an object, and thus do not rely on the state of an object to work correctly, they are sometimes called stateless methods, which is just another way of saying that the method is a procedural method.

From the discussion of stateless methods in the previous paragraph, it should be obvious that the move method as it is implemented has a problem. The move method is not stateless, and it cannot be static as it has a state that is maintained between calls to the move method and the draw method. This state is kept in two places in the object: the myPath variable and the synchronized lock for the object. Both of these variables are needed to make the move method work correctly. To see this, consider Exhibit 3 (Program10.3), which implements movement of objects of two different classes: a Ball class and a Square class. Each of these classes must contain its own move methods so that objects of both types have access to a unique object lock and myPath variable. It is not possible to make this method static and available to both classes, thus it really is not stateless.

Exhibit 3: Program10.3: Ball and Square Classes, Each Implementing Its Own Move Method

start example

 import java.awt.*; import java.util.*; import animator.*; public class MoveObjects {   /**    * The main method just creates the animator and the ball    * threads and starts them running.    */   public static void main(String args[]) {    Animator animator = new Animator();    (new Thread(new Ball(animator))).start();    (new Thread(new Square(animator))).start();    animator.setVisible(true);   } } class Ball implements DrawListener, Runnable {   private Path myPath;   private Animator animator;   /** Constructor. Note that we need to register with the    *  animator through the run method.    */   public Ball(Animator animator) {    this.animator = animator;   }   /**    * The run method simulates an asynchronous ball. The    * myPath variable is set here and used in the draw method    * and is intended to coordinate the ball thread running in    * this method and the GUI thread (from the animator)    * running in the draw method. This works correctly.    */   public void run() {     // Set an initial point to draw this object, and then add it     // to the animator.     myPath = new StraightLinePath(10, 205, 10, 205, 1);     animator.addDrawListener(this);     Random random = new Random(System.currentTimeMillis());     try {       while(true) {         move(new StraightLinePath(410, 205, 10, 205, 50));         Thread.sleep(random.nextInt(10000));         move(new StraightLinePath(10, 205, 410, 205, 50));         Thread.sleep(random.nextInt(10000));       }     } catch (InterruptedException e) {     }   }   /**    *  This method allows a thread to move to completion before    *  it continues. A Path variable is defined and then a wait is    *  done. This wait is matched by a notify in the draw method    *  when this thread has reached the end of the path,    *  releasing the thread to continue running.    */   public synchronized void move(Path path) {     myPath = path;     try {      wait();     } catch (InterruptedException e) {     }   }   /**    * Draw is called each time through the animator loop to draw    * the object. It simply uses the path to calculate the    * position of this object and then draws itself at that    * position. When the end of the path is reached, it notifies    * the ball thread.    */   public synchronized void draw(DrawEvent de) {     Point p = myPath.nextPosition();     Graphics g = de.getGraphics();     g.setColor(Color.red);     g.fillOval((int)p.getX(), (int)p.getY(), 15, 15);     if (! myPath.hasMoreSteps())       notify();   } } class Square implements DrawListener, Runnable {  private Path myPath;  private Animator animator;  /** Constructor. Note that we need to register with the   *  animator through the run method.   */  public Square(Animator animator) {    this.animator = animator;  }  /**   *  The run method simulates an asynchronous ball. The   *  myPath variable is set here and used in the draw method   *  and is intended to coordinate the ball thread running in   *  this method and the GUI thread (from the animator)   *  running in the draw method. This works correctly.   */  public void run() {    // Set an initial point to draw this object, and then add it    // to the animator.    myPath = new StraightLinePath(10, 205, 10, 205, 1);     animator.addDrawListener(this);     Random random = new Random(System.currentTimeMillis());     try {      while(true) {       move(new StraightLinePath(10, 205, 410, 205, 50));       Thread.sleep(random.nextInt(10000));       move(new StraightLinePath(410, 205, 10, 205, 50));       Thread.sleep(random.nextInt(10000));      }     } catch (InterruptedException e) {     }   }   /**    * This method allows a thread to move to completion before    * it continues. A path variable is defined and then a wait is    * done. This wait is matched by a notify in the draw method    * when this thread has reached the end of the path,    * releasing the thread to continue running.    */   public synchronized void move(Path path) {     myPath = path;     try {      wait();     } catch (InterruptedException e) {     }   }   /**    * Draw is called each time through the animator loop to draw    * the object. It simply uses the path to calculate the    * position of this object and then draws itself at that    * position. When the end of the path is reached, it notifies    * the ball thread.    */   public synchronized void draw(DrawEvent de) {     Point p = myPath.nextPosition();     Graphics g = de.getGraphics();     g.setColor(Color.red);     g.fillRect((int)p.getX(), (int)p.getY(), 15, 15);     if (! myPath.hasMoreSteps())       notify();   } } 

end example

Because the move method is not stateless, it is not procedural and thus not a good candidate for method reuse. The only way to implement this type of state-dependent reuse is to create object definitions that allow the method requiring the state to be included in the object that uses it. The need to maintain a state to implement reuse is a common problem in OOP languages such as Java. Many objects need to maintain a state. As was shown in Chapters 3 and 7, this is always true of components, but it is also true of many utility objects that are not components. For example, a StringTokenizer needs to keep track of its current position in parsing a string, and a Vector needs to keep track of what elements are currently stored. When these objects are reused it is necessary to find a way to include the state of the reused object inside of the object that is using the utilities provided. This can be done in two ways in OOP, by composition or classification. Section 10.7 shows how classification can be used to allow object reuse for the move method, and Section 10.8 shows the same move method reuse using composition.

One last note about the state needed by the move method: As mentioned earlier, the state that must be maintained not only is kept by a variable (in this case, the myPath variable), but also involves the lock on this object. This second dependency on the object lock might not be obvious, as the variable that implements it is hidden. As will be seen later, though, it impacts the way the methods in this object are designed.



 < Day Day Up > 



Creating Components. Object Oriented, Concurrent, and Distributed Computing in Java
The .NET Developers Guide to Directory Services Programming
ISBN: 849314992
EAN: 2147483647
Year: 2003
Pages: 162

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net