10.8 Composition: Reuse by Delegation

 < Day Day Up > 



10.8 Composition: Reuse by Delegation

A second way to accomplish object reuse is called composition. Composition requires that we first create a class that will instantiate a utility object that is completely unattached to any other class. The utility class is then included as a variable in any encapsulating class. When the encapsulating class is instantiated, the resulting object is composed of other objects, hence the term composition.

Because this utility class is not part of the definition of the implementing class, the data for the utility object represents an entirely different object than for the object that uses this class. Therefore, the utility object is completely separate from the enclosing object, and the utility object can be completely encapsulated, thus protecting the utility object from interactions with the using object. Reuse of the utility object is accomplished when the methods of the utility object are called from the encapsulating object, a process that is known as delegation.

To see how composition works, consider Exhibit 6 (Program10.5a), which implements the MoveController using composition, and Exhibit 7 (Program10.5b), which is a ConcurrentBall program using this MoveController. In Exhibit 7 (Program10.5b) the ConcurrentBall program defines a variable, mc, which is a MoveController object that is a completely separate object from the ConcurrentBall object. The ConcurrentBall object in this case is composed of the MoveController object, and it reuses the MoveController by delegating to the MoveController methods the behaviors it wishes to use — in this case, the move, hasMoreSteps, and nextPosition methods.

Exhibit 6: Program10.5a: MoveController Utility Object Showing Reuse by Composition

start example

 import java.awt.Graphics; import java.awt.Point; import animator.*; public final class MoveController {   private Path myPath;   private boolean doNotify = false;   /**    *  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 {       doNotify = true;       wait();     } catch (InterruptedException e) {     }   }   /**    *  Check if the path has more steps in it. Note that it is    *  not necessary to call this method in the using class, as    *  the notification is handled in the nextPosition call, but    *  because the myPath variable is encapsulated, this delegate    *  call allows the program to still have access to this    *  method if it needs it.    */   public boolean hasMoreSteps() {     return myPath.hasMoreSteps();   }   /**    *  This method gets the nextPosition from the myPath variable    *  and does a notify if the end of the path has been reached.    *  Note that this method does more than simply get the    *  nextPosition; the name was kept from the Path to simplify    *  adding MoveController objects into existing programs.    */   public synchronized Point nextPosition() {    if (myPath ! = null && myPath.hasMoreSteps()) {      return myPath.nextPosition();    }    else if (myPath ! = null) {      if (doNotify) {        doNotify = false;        notify();      }      return myPath.nextPosition();    }    else      return new Point(0,0);   } } 

end example

Exhibit 7: Program10.5b: 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 implements DrawListener, Runnable {   private Animator animator;   private MoveController mc;   /** Constructor. Note that we need to register with the    *  animator through the run method.    */   public ConcurrentBall(Animator animator) {     this.animator = animator;     mc = new MoveController();   }   /**    *  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) {         mc.move(new StraightLinePath(410, 205, 10, 205, 50));         Thread.sleep(random.nextInt(10000));         mc.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) {     Point p = mc.nextPosition();     Graphics g = de.getGraphics();     g.setColor(Color.red);     g.fillOval((int)p.getX(), (int)p.getY(), 15, 15);   }   /**    * 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

The important thing to remember is that, unlike classification, where the MoveController and ConcurrentBall shared an object, in composition the MoveController object is a separate object from the ConcurrentBall object. The variable myPath and the object lock are not shared with the ConcurrentBall object. This is important in that it solves many of the problems of classification. First, the anomaly of the wait call unexpectedly freeing a lock on the object is avoided because there are two locks, and the lock that is freed will be the one limited to the MoveController object. Second, the problem of the program having access to the variables or needing to know details of the utility class is avoided because the details are completely encapsulated in the MoveController object, and the program using the utility method, the ConcurrentBall program, has no access to these details. This greatly enhances the safety of the program. Finally, the spaghetti that could result from overriding methods and variables is avoided because the methods and variables are clearly contained in the proper objects and can be referred to in those objects.

However, some costs are associated with using composition. The first is that, with classification, an object can be done with one allocation (as it is a single object), but with composition each object must be allocated separately, although the overhead for doing this is normally small when compared to the overall overhead involved in running a program, and separation of the parts of an object using composition can have some beneficial aspects, particularly if parts of the object could be run in a distributed computing environment such as RMI. The second cost is that to use a method in the utility class, it must be referred to with the variable representing the utility class object if you are in the method (e.g., mc.move vs. simply move). If objects other than the enclosing object are to have access to methods in the utility object, a separate method must be created in the enclosing object that simply delegates the work to the utility object. These problems indicate that reuse of the class requires slightly more code, and, while no practical difference in the ability to reuse the object exists, the argument is that the increase in the amount of code required could limit the amount of reuse that is actually implemented. To my mind, this is a poor argument for classification, considering its drawbacks for utility reuse.

One last point about composition must be considered. It is possible to argue that the same type of reuse that is done with composition can be implemented using classification. To do this, all of the variables in the class are made private, and all of the methods are made protected, as shown in Exhibit 8 (Program10.6a) and used in Exhibit 9 (Program10.6b). This is the equivalent of private inheritance in C++. While this classification solution appears to be the equivalent of the composition solution of Exhibit 6 (Program10.5a), there are two differences, one small and one very important. The first difference is that the MoveController class in Exhibit 8 (Program10.6a) requires that the program that implements it must use up its single extends clause that is allowed by Java. This probably in and of itself is enough to argue against this solution, particularly in Java.

Exhibit 8: Program10.6a: New Classification Implementation Using Encapsulation

start example

 import java.awt.Graphics; import java.awt.Point; import animator.*; abstract public class MoveController {   private Path myPath;   private boolean doNotify = false;   private Object notifyObject = new Object();   protected void move(Path path) {     synchronized(notifyObject) {       myPath = path;       try {         doNotify = true;         notifyObject.wait();       } catch (InterruptedException e) {       }     }   }   protected boolean hasMoreSteps() {     return myPath.hasMoreSteps();   } protected Point nextPosition() {     if (myPath ! = null && myPath.hasMoreSteps()) {       return myPath.nextPosition();     }     else if (myPath ! = null) {       if (doNotify) {         synchronized(notifyObject) {           doNotify = false;           notifyObject.notify();         }       }       return myPath.nextPosition();     }     else       return new Point(0,0);   } } 

end example

Exhibit 9: Program10.6b: Concurrent Ball Program Using MoveController from Problem10.5a

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) {     Point p = nextPosition();     Graphics g = de.getGraphics();     g.setColor(Color.red);     g.fillOval((int)p.getX(), (int)p.getY(), 15, 15);   }   /**    * 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

The second problem is much more critical. The classification solution in Exhibit 8 (Program10.6a) does not solve the problem of the two classes making one object which first appeared in Exhibit 4 (Program10.4a). For example, because the two classes make up one object, the solution in Exhibit 8 (Program10.6a) had to add another variable for the synchronization lock because the lock for the object is still shared, as they are still the same object. This shows that if the actual object that is used is shared (as in classification), it is not possible to implement a properly encapsulated object. Leakage of object information will always occur between the classes in Java.

All of this leads to the general rule that a programmer should never subclass what is essentially a utility object. The reason for doing classification is to create type hierarchies, where objects are actually made of attributes from several classes, as is covered in Chapter 11. It is the essence of the "is-a" and "has-a" relationships of objects. That type of hierarchy is not present when using a utility class. It will always be as valid to say a Button has a vector as saying a Button is a Vector, and it is all meaningless for utility classes. The question is not one of applying an inappropriate rule, but rather what works best in the design and implementation of a program. As we have seen already, utility classes should generally be implemented using composition, not classification, because it generally results in better programs.



 < 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