10.7 Classification: Reuse by Extension

 < Day Day Up > 



10.7 Classification: Reuse by Extension

The first way to reuse methods that require state data is to create a base class that can be used as part of the definition of the class for the object to be created. Thus, the class being defined consists of the base class and a class that extends the properties of the base class, creating a class with properties from both the base and extended class. Objects that instantiate this extended class have all the properties of the base and extended class. This is called inheritance, or classification, and is achieved in Java using the extends clause. It is important to remember that when classification is used, two objects (one for the base class and one for the extended class) are not created; rather, a single object is created that has the properties of both objects.

Exhibit 4 (Program10.4a), the MoveController object, is an example of how the move method can be included in the current object through classification. Exhibit 5 (Program10.4b) is an example of a program that uses the MoveController object of Exhibit 4 (Program10.4a). Here, the move method is in the MoveController object. Because the ConcurrentBall object extends the MoveController object, all of the methods and variables for the MoveController object become part of the ConcurrentBall object. Thus, the myPath variable and the object lock used by the MoveController are also used in the draw method for the ConcurrentBall class. Because the myPath and object lock are shared between the MoveController and the Concurrent Ball, the move method now has a state that can be kept between invocations. This allows the MoveController class to be extended by any object, and the move to be reused without being copied to those objects.

Exhibit 4: Program10.4a: MoveController Utility Object Showing Reuse by Classification

start example

 import java.awt.Graphics; import java.awt.Point; import animator.*; abstract public class MoveController {   Path myPath;   /**    *  The move method simply saves the parameter path into the    *  variable myPath and does the wait, much like its    *  procedural cousin.    */   public synchronized void move(Path path) {     myPath = path;     try {       // Note that this wait is wrong, as it could       // unintentionally drop a lock on the object that is held       // by an enclosing synchronized block.       wait();     } catch (InterruptedException e) {     }   } } 

end example

Exhibit 5: Program10.4b: Program That Uses a MoveController Object

start example

 import java.util.Random; import java.awt.Graphics; import java.awt.Point; import java.awt.Color; import animator.*; public class ConcurrentBall extends MoveController        implements DrawListener, Runnable {   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() {    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) {    }   }   /**    * 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) {     if (myPath = = null)       return;     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

This type of reuse has the advantage that every data item and method in the base class (in this case, the MoveController class) can be available when using the extended object; therefore, extra methods do not have to be created to delegate actions to the utility object. This increases the possibility for reuse of the base class. Such a potential for simple reuse is one of the reasons why classification is favored by a number of programmers.

Using classification in object reuse does have a number of significant drawbacks, however. First, the details of the base class must be known and taken advantage of by the class that extends it. This means that the behavior of the base class cannot be encapsulated. [1] Encapsulation occurs when the variables of an object are all declared private; access to them is hidden inside the object and is only accessible through a well-defined method. This means that the programmer does not need to know the details of the object being used, and the object is protected from the programmer accidentally or intentionally creating invalid states in the object; however, in the case of the MoveController implemented in Exhibit 4 (Program10.4a), the programmer writing a class that uses the MoveController has to know how it works and is expected to provide the extra code in the draw method to complete the execution of the move method.

Because the data for the MoveController object is accessible to the extending class, it is even possible for the programmer to break the object by improperly changing data that they should probably not have access to. For example, they could reset the path in the middle of a movement without doing a notify, possibly deadlocking the programming. The data for the MoveController class can be misused in an even more insidious way. Because the move method calls the wait method, the object lock is released, but the lock that is released is not simply the lock for the MoveController object. The one object in this case is an instance of the class created by combining the MoveController and the ConcurrentBall classes; therefore, when the wait method is called for this object, the lock is dropped for the entire object. If the method calling wait is itself called from a synchronized method, then the object lock is dropped for all the synchronized blocks. To see this, consider the following code fragment using the swap method from Chapter 2:

   public void synchronized swap(SwapObject a, SwapObject b) {     tmp = a.val1;     move(100,100,200,200,10);     val2 = val1;     val1 = tmp;   } 

Here, the swap method was synchronized to protect against a race condition when using the tmp variable. However, if the class implementing the swap method extends the MoveController object to gain access to the move method, the same object is used for the move and swap methods, even though the methods are in different classes. This means that when the move method is called the programmer has unintentionally done a wait in a critical section (as move calls wait), dropping the object's lock and introducing a race condition into their program. This unintended side effect shows that, when using classification to extend an object, the programmer must understand the details of the object being extended. Worse, the programmer must understand things that are implied in the implementation, such as what it means to drop a synchronized lock in a base class.

This example of how the synchronized block can cause unexpected side effects points out another problem with classification. Classification can result in the OOP version of spaghetti code. Because variables can be accessed in subclasses, control over where variables are changed is no longer clear. Methods that are overridden can result in the programmer not being able to easily tell what method is actually being called. Variables that are redefined can lead programmers to make invalid assumptions about what variables are accessed, and the deeper the inheritance hierarchy in the program the more convoluted these relationships can become.

All of the problems with classification aside, it is still a much better mechanism for utility reuse than copying procedures between programs. And, if a better solution did not exist, object reuse by classification would still be a good solution; however, for utility reuse a better mechanism for object reuse does exist: composition.

[1]It can be argued that encapsulation is possible for a base class, but it would have no advantage over composition and in fact would have several disadvantages, as is shown in Section 10.8.



 < 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