- Reviewing the different conditions for inheritance, for association and for composition.
- Implementing association and composition.
IntroductionAn important aspect of OO is the re-use of existing classes, typically through inheritance or delegation. Inheritance is an integral part of modern OO languages and in previous chapters we have looked at reuse through inheritance in terms of extension and specialisation. When we need only a few aspects of a class, inheritance may be inappropriate for reuse since it exposes not only the aspects we need but also all those that we do not need and that should remain hidden. In these cases it is often worth considering the alternative of delegation, through either association or composition. This possibility is the focus of this chapter.
At code level, association and composition are similar: the main differences are in their application and the degree of encapsulation. A consequence of these differences is that association often involves making shallow copies of objects while composition involves making deep copies, and so in this chapter well also explore the question of how to copy an object.
Like inheritance, association and composition allow re-use. However, while re-use through inheritance exposes its ancestor in its entirety, association and composition allow us to expose only selected parts of another class.
Unlike inheritance, association and composition are not supported directly by the language. The programmer must specifically create the pathways for using the associated or constituent objects by creating links to them and by coding methods to provide the features that are to be exposed.
In composition, the programmer must go a step further by creating the constituent object and explicitly propagating operations on the composed class to the constituents, such as freeing the constituent objects when the composed object is freed.
All this means that both association and composition require more initial programming than inheritance but they lead to a less rigid relationship, and so often simplify future changes and evolution.
As we have seen, inheritance implies an IsA relationship. Association often implies a UsesA relationship while composition may imply a HasA relationship. Inheritance leads to a single object that has all its own characteristics plus those it inherits, and that performs all the required operations itself. Association and composition each lead to a group of objects, with the responsibility for the different operations distributed between these objects. With association there is a sense of independence and cooperation between these objects.
Composition emphasises encapsulation: a composed object conceptually owns its constituents and, since it creates and frees its constituents, the lifetimes of the constituent objects are bounded by the lifetime of the composed object.
Example 9.1 AssociationAssume that we have a system that contains a TCustomer class. We now need a TSale class that has the same data fields as a TCustomer along with an Amount data field.
To allow reuse we could use implementation inheritance, association or composition. In each case we define a new TSale class with an Amount data field. For inheritance we derive TSale from TCustomer. For association we provide a link to an existing TCustomer. For composition we create a new TCustomer object that we link to and whose data fields we set appropriately.
Which of these three approaches is best? Semantically, TSale IsNotA TCustomer, and using inheritance would give rise to anti-pattern 7.4 (construction convenience). Association and composition are both semantically sound alternatives. In this example we use association and use composition in the next.
Ex 9.1 step 1 The TCustomer classFor the sake of the example, we use a very simple TCustomer class. It uses directly mapped properties and so doesnt even have access methods. (In a realistic situation it would be much more complex, with additional data and with access methods to provide validation.) Start a new application and include a second unit in the project, coded as follows:
unit CustomerU; interface type TCustomer = class(TObject) private FPhoneNo: string; FName: string; public property Name: string read FName write FName; property PhoneNo: string read FPhoneNo write FPhoneNo; end; implementation end.