moock.org |
essential actionscript 2.0 |
Inheritance vs Composition
In this chapter we focused most of our attention on one type of inter-object relationship: inheritance. But inheritance isn't the only game in town. Composition, an alternative form of inter-object relationship, often competes with inheritance as an OOP design technique. In composition, one class (the "front end class") stores an instance of another class (the "back end class") in an instance property. The front end class delegates work to the back end class by invoking methods on that instance. Here's the basic approach, shown in code:
// The back end class is analogous // to the superclass in inheritance. class BackEnd { public function doSomething () { } } // The front end class is analogous // to the subclass in inheritance. class FrontEnd { // An instance of the back end class will be stored in // a private instance property, in this case called 'be'. private var be:BackEnd; // The constructor creates the instance of the back end class. public function FrontEnd () { be = new BackEnd(); } // This method delegates work to BackEnd.doSomething(). public function doSomething () { be.doSomething(); } }
Notice that the FrontEnd class does not extend the BackEnd class. Composition does not require or use its own special syntax, as inheritance does. Furthermore, the front end class may use only some of the methods of the back end class, or it may use all of them, or it may add its own unrelated methods. The method names in the front end class might exactly match those in the back end class, or they might be completely different. The front end class can constrain, extend, or redefine the back end class's features, just like a subclass in inheritance.
For example, earlier in this chapter we showed how, using inheritance, a Square class could constrain the behavior of a Rectangle class. Example 6-3 shows how that same class relationship can be implemented with composition instead of inheritance. In the example, notice that the Rectangle class is unchanged. But this time, the Square class does not extend Rectangle. Instead, it defines a property, r, that contains a Rectangle instance. All operations on r are filtered through Square's public methods. The Square class forwards, or delegates, method calls to r.
Example 6-3. An Example Composition Relationship // The Rectangle class is unchanged. class Rectangle { private var w:Number = 0; private var h:Number = 0; public function Rectangle (width:Number, height:Number) { setSize(width, height); } public function setSize (newW:Number, newH:Number):Void { w = newW; h = newH; } public function getArea ():Number { return w * h; } } // Here's the new Square class. class Square { private var r:Rectangle; public function Square (side:Number) { r = new Rectangle(side, side); } // Note that we use our earlier version of Square.setSize(), which defines two // parameters, newW and newH. We stick to the original implementation for the // sake of direct comparison, rather than implementing a more elegant version, // which would define a single sideLength parameter only. public function setSize (newW:Number, newH:Number):Void { if (newW == newH) { r.setSize(newW, newH); } } public function getArea ():Number { return r.getArea(); } }
Is-A, Has-A, and Uses-A
In OOP parlance, an inheritance relationship is known colloquially as an "Is-A" relationship because, from a datatype perspective, the subclass can be literally be seen as being an instance of the superclass (i.e., the subclass can be used wherever the superclass is expected). In our earlier polymorphic example, a Circle "is a" Shape because the Circle class inherits from the Shape class, and can be used anywhere a Shape is used.
A composition relationship is known as a "Has-A" relationship because the front end class stores an instance of the back end class (e.g., a ChessBoard "has a" Tile). The "Has-A" relationship should not be confused with the "Uses-A" relationship, where a class instantiates an object of another class, but does not store it in an instance property. In "Uses-A", the class uses the object and throws it away. For example, a Circle might store its numeric color in a property, col ("Has-A"), but then use a Color object temporarily to actually set that color on screen ("Uses-A").