Using and Abusing Inheritance - Delphi OOP Part 7 - Chapter 15
Generalisation
The third use of subclassing we mentioned is to set up a generalisation hierarchy. This comes about when we note similarities between different objects during design. It is better not to recode these similarities in each of the classes. So we create a possibly artificial parent class to implement the aspects the children share.The general functionality that holds for all the children we implement in the parent.
Each child then gets it own specific functionality through extension or specialisation as just described. We may find that some of these parents also share common aspects, and so these can then be generalised up again to a higher level. A good example of several levels of generalisation is Delphi?s VCL as discussed in chapter 2. It is deeper than a hierarchy of application objects would ideally be, but it represents a standard library, a situation where deeper hierarchies are generally acceptable.
The children at the bottom of a generalisation hierarchy correspond to real world objects. However, the higher levels may or may not represent real world objects. Often they don?t. And so a generalisation hierarchy is a computer artefact to help model a real world situation on the computer and to provide an effective, runnable model. It provides a lot of power, but because of its artificiality we must take care to maintain the IsA relationship between each level of the hierarchy so as not to interfere with the semantics of the situation it is modelling.
Example
We?ll look briefly at an example representing real world objects. Assume that we have an access system with a TEmployeeCard class that represents cards that employees wear and present at access control points. Because of changing requirements we now need to introduce a TVisitorCard class.TEmployeeCard: IDNO, Name, EntryTime, ValidExit()
TVisitorCard: IDNO, EntryTime, SignOff, ValidExit()
When we compare these classes we see that they have a lot in common since both have IDNo and EntryTime fields and a ValidExit() method. We can therefore derive TVisitorCard from TEmployeeCard. We then extend TEmployeeCard by adding to TVisitorCard the SignOff data field and specialise it by overriding ValidExit() to add a check for the SignOff status to all the checks already present in TEmployeeCard?s ValidExit().
So, by taking advantage of the hierarchy and inheritance, we can reuse the IDNo and EntryTime data fields, override ValidExit() and add SignOff.
While this solution is tempting because it allows reuse, there is a problem. The subclass exposes the entire superclass. Because of inheritance, TVisitorCard now has a Name property as well even though this is not specified as being part of it. There is also a problem because of substitution. If at any time that the program is dealing with a TEmployeeCard it is actually addressing a TVisitorCard, which will be legal because of substitution, the Name property may hold an invalid value, creating a potential for error.
When we analyse the situation we see that the problem arises because inheritance is an IsA relationship while TVisitorCard IsNotA TEmployeeCard. It is not a complete subclass of the parent since it does not have the Name property. So TVisitorCard is not a subtype of TEmployeeCard, and while deriving TVisitorCard from TEmployeeCard is convenient from the perspective of the implementation, it is not semantically correct. As far as possible, our computer model should retain the meaning of the real world.
Fortunately there is a simple alternative here that allows us to maintain most of the advantages of inheritance while retaining the subtype relationships and the semantics. We generalise the commonality between these two classes into a new class, TIDCard. (It may not exist in the actual real world situation.) We now derive both TEmployeeCard and TVisitorCard from TIDCard and use them to implement the necessary extension and specialisation. TVisitorCard is no longer derived from TEmployeeCard and so it no longer exposes the Name property and it cannot substitute for TEmployeeCard. Where the program needs to work individually with employees and visitors it can do so through the subclasses TEmployeeCard and TVisitorCard. Where it needs to perform operations applicable to both employees and visitors, it does so through the superclass TIDCard.