Вы находитесь на странице: 1из 20

Chapter 7 Using and abusing

inheritance

Main concepts

– Clarify differences between subclassing and subtyping.


– Use each appropriately.
– Inheritance anti-patterns.

Chapter contents

Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Implementation inheritance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Interface inheritance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
Inheritance anti-patterns: Abuses of implementation inheritance . . . . . . . . . . . . . . . . . . . . . . . 8
Violating the IsA rule . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
Anti-Pattern 7.1 Incomplete (constrained) subclass . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
Anti-Pattern 7.2 Limiting visibility . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
The problem of roles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
Anti-Pattern 7.3 Subclassing a role . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
Convenience versus semantics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
Anti-Pattern 7.4 Construction convenience . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
Requirements / Tests for implementation inheritance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
Underuse of interface inheritance:
Inheritance considered beneficial . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14

Using and abusing inheritance (19 Aug 2006, all rights reserved) Chapter 7, Page 1
Anti-Pattern 7.5 Conditionals suspect . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
Summary of inheritance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
Chapter summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
Reference . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
Problems . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
Problem 7.1 Study Chapter 7 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16

Introduction

Over the last few chapters we have been looking at inheritance, both subclassing and
subtyping. Programmers often do not distinguish between these two forms since they are
implemented in similar ways and are often used in combination. However subclassing (class
or implementation inheritance) can easily be overused and subtyping (interface inheritance)
can easily be underused. Because of this we lose potential benefits of OO and develop
systems that are more rigid and error prone than they need to be.
In this chapter we distinguish between these two forms of inheritance and look at some
common errors in using them.

Implementation inheritance

Implementation inheritance is an alternative name for subclassing or class inheritance. It is


useful as a name because it emphasises that we use it in order to derive one class from
another. It creates a hierarchy where the new class inherits the implementation (ie the
structure & behaviour) from another class, allowing the programmer to reuse existing code.
The subclass therefore needs only to implement differences from the superclass, which it does
by overriding or extending the inherited implementation (figure 1). The purpose of
implementation inheritance is therefore code reuse.

Figure 1 Implementation or
Class inheritance

Chapter 7, Page 2 Object orientation with Delphi (all rights reserved)


In using implementation inheritance, an important perspective to keep in mind is that it
implements the IsA (or KindOf) relationship. This has a number of important consequences,
and breaking the IsA relationship is one of the main reasons for inappropriate use of
inheritance.
Because of inheritance, the Child IsA Parent. This means that the child has the same innate
character as the parent and is not merely a role of the parent. It is the entire parent plus more:
the child has all the attributes and behaviour of the parent plus additional attributes and
behaviour of its own. Because the child has these additional attributes and behaviour it is
more tightly specified than the parent and so is a specialisation of the parent.
Typical uses of implementation inheritance that accord with the IsA relationship are
subclassing for extension and specialisation, and for creating a generalisation hierarchy.

Extension
In inheritance for extension, the child adds new functionality without making any changes to
the parent’s existing functionality. In figure 1, this corresponds to the More Data and More
Methods of SubClass1, and to the Other Data and Other Methods of SubClass2. (We
introduced this concept in example 2.2 in the context of VFI: example 2.2 step 1 adds a
CheckBox while example 2.2 step 2 adds an UpDown.) While inheritance for extension
offers considerable potential for code reuse, if it is overused it leads to very deep, rigid
hierarchies that are difficult to modify.

Specialisation
In inheritance for specialisation, the child is a special case of the parent and so overrides some
of the parent’s methods as in Override Methods 1 and 2 that override the Parent Class’s
Methods in figure 1. There are two forms of overriding. The one simply specialises the
parent’s method. It invokes the method it is overriding in the parent (using the inherited
keyword in Delphi) and then adds some additional operations. (We introduced this concept
in example 2.3 in the context of VFI. We saw another example of this in example 4.2 step 1
where the subclass’s constructor first invokes the superclass’s constructor before initialising
its additional data fields and uses the superclass's destructor after showing a message.) The
other form of specialisation is where the overriding method completely replaces the parent’s
method. We saw this in example 6.1, where the subclasses’ GetKind methods completely
replace the superclass’s GetKind method.
Because of the hierarchy, a subclass can substitute for a superclass. So we must be very
careful when overriding parent methods to make sure that the child can still operate
satisfactorily in the place of the parent even with its overriding methods. If the overriding
destroys the parental type by introducing inappropriate behaviour, substitution is no longer

Using and abusing inheritance (19 Aug 2006, all rights reserved) Chapter 7, Page 3
valid semantically and we have a potentially error-prone situation in the code. (Anti-pattern
7.2 below returns to this issue.)

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 (figure 2).

Figure 2 Requirement for a


new class

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().

Chapter 7, Page 4 Object orientation with Delphi (all rights reserved)


So, by taking advantage of the hierarchy and inheritance, we can reuse the ID No and
Entry Time data fields, override ValidExit() and add SignOff (figure 3).

Figure 3 A possible inheritance


structure

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 (figure 2). 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 (figure 4).
(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.

Using and abusing inheritance (19 Aug 2006, all rights reserved) Chapter 7, Page 5
Figure 4 Using generalisation
to maintain subtyping

Interface inheritance

With implementation inheritance we reuse the superclass’s implementation. In contrast to


this, interface inheritance, which we cover in this section, reuses the supertype’s interface (or
signature or type). However, it does not reuse the implementation, since each subtype
implements this interface differently according to its own needs. We saw an example of this
in example 6.4, where we have an abstract class TMyShape. TMyShape defines the
procedure Draw(), but does not implement it. The subclasses TMyEllipse and TMyRectangle
each implement Draw() differently. The client class, in this case the user interface, simply
sends a Draw() message to a TMyShape. Through polymorphic substitution, either a
TMyEllipse or a TMyRectangle can respond appropriately to this Draw() message.
Figure 5 represents this principle.

Figure 5 Interface inheritance

The abstract superclass acts as a type specification, and so has abstract methods. Since these
methods are specifying an interface, they are virtual: they have no implementation in the
parent and the parent cannot be instantiated. (In UML, abstractions are shown in italics and
so both Abstract Parent and Abstract Methods() are italicised in figure 5.) The concrete
subclasses override the Abstract Methods() with their Concrete Methods() and so can be
instantiated.

Chapter 7, Page 6 Object orientation with Delphi (all rights reserved)


In summary, the child classes inherit the (abstract) class signature of the superclass. They
provide concrete methods of the same type as the abstract methods, but each child class has
its own, usually different, implementation.

Example
Let’s look at how one might decide to use interface inheritance while designing a system.
Take a system where specific benefits differ between monthly paid and weekly paid
workers although the general principles of vacation leave, study leave and sick leave apply
to both groups (figure 6).

Figure 6 Same interface but


different implementations

In object modelling terms, both of these have the same interface (or type) but have different
implementations of the operations. To introduce the possibility of subtyping, we create an
abstract type specifying the interface and leave the concrete implementations to the
subtypes (figure 7).

Figure 7 Abstract type with


concrete subtypes

This inheritance structure is designed with substitution in mind since either subtype can
meaningfully substitute for the supertype. So client objects program to the supertype
interface specification even though it is abstract and has no implementation. At runtime, one
of the subtypes substitutes for the supertype and performs the required operations.
We use interface inheritance when classes have the same kinds of operation, and so have
a consistent interface, but the details of the operations are different. Through dynamic
binding the behaviour depends on the runtime identity of the class currently substituting
for the supertype, giving rise to polymorphism.

Using and abusing inheritance (19 Aug 2006, all rights reserved) Chapter 7, Page 7
Inheritance anti-patterns: Abuses of implementation
inheritance

As we have seen, inheritance is very powerful and can provide significant benefits in
programming. However, there are times when it is misused, leading to problems that could
possibly be avoided. This section considers some of these misuses.

Violating the IsA rule

Many of the situations where class inheritance is overused and/or incorrectly used can be
traced back to a violation of the IsA rule, with the consequence that the subclassing
invalidates the subtyping. The first anti-pattern is a direct consequence of breaking the IsA
relationship.

Anti-Pattern 7.1 Incomplete (constrained) subclass

Figure 8 ClassB is an
incomplete subclass of ClassA

This anti-pattern is also referred to as false generalisation.


In a particular situation, a subclass (ClassB) may require only part of the functionality of
the superclass (ClassA). However, the entire superclass is inherited and visible, since it is
not possible to restrict its visibility1 . This means that inherited operations unrelated to the
subclass become feasible, possibly with disastrous results.

Therefore,

to prevent potentially erroneous operation, revise the hierarchy so that the semantics of
the application are maintained. There are at least three possibilities:

1
In neither Delphi nor Java can a subclass reduce the visibility of any superclass’s data or
methods without resorting to overriding, a situation that leads to anti-pattern 7.2. C++ provides
a mechanism within the language to restrict visibility but this should be used carefully.

Chapter 7, Page 8 Object orientation with Delphi (all rights reserved)


The subclass is a restricted version of the superclass: In this case one can consider inverting the
hierarchy so that the more restricted class becomes the superclass (figure 9).

Figure 9 Invert the hierarchy

Considerable commonality, but each has characteristics not found the other: In this case create an
artificial parent class to house all the common functionality. Derive the subclasses from
this, each one implementing its additional characteristics (ie characteristics not
implemented as common characteristics in the parent) (figure 10). Figures 2 and 4 above
give an example of this approach. In figure 2 neither TEmployeeCard nor TVisitorCard
contains all the features of the other. This is resolved by moving the shared attributes and
behaviour to a specially constructed parent of both classes (figure 4).

Figure 10 Change the


generalisation

Only slight commonality between the classes: Compose ClassB from ClassA (figure 11) This
allows ClassB to avoid reimplementing the shared behaviour by delegating the shared
behaviour to ClassA (instead of inheriting it from ClassA). Delegation and composition
are discussed in chapter 8.

Figure 11 ClassB composed


from ClassA

Anti-Pattern 7.2 Limiting visibility

One of the problems when a subclass is a restricted version of the superclass is that the
subclass exposes aspects of the superclass that are not related to the subclass. (The subclass
cannot reduce the visibility of the superclass.) Anti-pattern 7.1 provides three possible

Using and abusing inheritance (19 Aug 2006, all rights reserved) Chapter 7, Page 9
solutions to this problem. A fourth possibility exists, but that creates its own set of
undesirable consequences. One can, for all the behaviour in ClassA (figure 8) that is not
relevant to ClassB, provide an overriding method in ClassB that invalidates the
inappropriate matching method in ClassA. If Method1 in ClassA does not apply to ClassB,
ClassB overrides Method1 and performs a null or different operation or throws an
exception. So, ClassB declares a method of the following format:

procedure ClassB.Method1;
{
{ Throw Exception; }
// OR
{ Null operation; }
// OR
{ Different operation; }
}

There are several potential problems in this approach. To limit visibility like this requires
constant vigilance and intervention by the programmer, who must identify and override all
the inherited methods that are not appropriate in the subclass. Leaving out some of these
overrides can lead to serious errors. Invalidating or changing the behaviour in the subclass
also affects the semantics, and the subclass no longer IsA superclass. Since the subclass is
now not a proper subtype of the superclass, the subclass can no longer substitute for the
superclass for the invalidated or changed operations. If the programmer attempts to use
polymorphic substitution, this can lead to serious errors.

Therefore,

in a subclass do not invalidate any inherited method or override it in a way inappropriate


to the superclass since doing so will destroy the subtyping and make polymorphic
operations error-prone. Instead, restructure the hierarchy along one of the possibilities
suggested as solutions in anti-pattern 7.1.

The problem of roles

Another problem arises when subclasses are used to model roles instead of subclasses. This
can be quite subtle. For example, the hierarchy in figure 12, where Table and Chair are
subclasses of Furniture, seems quite acceptable.

Chapter 7, Page 10 Object orientation with Delphi (all rights reserved)


Figure 12 Subclassing for
identity

By comparison, are Monthly paid and Weekly paid employees subclasses of Employee?

Figure 13 Subclassing roles

Although figures 12 and 13 seem so similar, there is a subtle difference between them, and
that is the lifetime of a role. In figure 12, once a chair always a chair – the chair won’t
suddenly be changed into a table. However, in figure 13 it is quite possible that the
employee who currently has the role of weekly paid employee may be appointed to a
different role, and so become a monthly paid employee. If this happens in a system with the
structure shown in figure 13, it becomes necessary to create a new monthly paid employee
object, to copy all the relevant data from the weekly paid employee object, and then to
destroy the weekly paid object. This is a lot of work with considerable potential for error.

Anti-Pattern 7.3 Subclassing a role

When an object takes on different roles or functions under different circumstances, it is


clumsy to model these various roles as subclasses, since a change in role will then
necessitate the creation of a new object for the new role, copying the state of the old role to
the new role, and then deleting the object modelling the old role. Where a set of properties
and behaviour are an inherent, unchanging characteristic they typically constitute a
subclass. Where the properties and behaviour change over time, they constitute a role.

Using and abusing inheritance (19 Aug 2006, all rights reserved) Chapter 7, Page 11
Therefore,

instead of subclassing a role, separate the roles into a different hierarchy that uses
interface inheritance. Make the root of this separate hierarchy an attribute of the original
superclass (eg figure 14 in contrast to figure 13). By substitution, the value of this attribute
can be set to any of the subtypes. Thus a role change is accommodated by changing the
attribute to refer to a different subtype rather than through a create, copy, delete cycle.

Figure 14 Separating out the


roles

(Several patterns, such as the Strategy, the Player-Role, the Abstraction-Occurrence and the
State patterns, use a similar technique to solve different problems and we will explore this
technique more fully later on.)

Convenience versus semantics

If one’s only focus in adopting OO is to re-use as much existing code as possible, it is


tempting to re-use whatever functionality is already present wherever we can find it.
Unfortunately this can lead to inheritance hierarchies that are quite complex and do not
necessarily reflect accurately the situation they are modelling.
A mild example of this occurred at the beginning of this chapter, where the new
requirement of a TVisitorCard was very conveniently derived from the existing
TEmployeeCard as a basis for adding the new functionality (figure 3). This simplifies the
implementation but introduces semantic problems because a TVisitorCard is not a
TEmployeeCard. Because of the inheritance, any change to TEmployeeCard directly affects
TVisitorCard. Because of substitution, it is not possible to have operations that apply only to
a TVisitorCard and not to a TEmployeeCard.
This analysis leads to the following anti-pattern.

Chapter 7, Page 12 Object orientation with Delphi (all rights reserved)


Anti-Pattern 7.4 Construction convenience

For the sake of convenience in construction, it is tempting to derive a new class from an
existing class even when there is only a limited conceptual relationship between the two
classes. However, because there is not a valid IsA relationship between the two classes, this
can lead to a variety of problems (as indicated above) because of the tight coupling that is
introduced when one class is derived from another.

Therefore,

where a valid IsA relationship does not exist between two classes, do not derive one from
the other. Instead, depending on the strength of the relationship, consider various forms of
restructuring as suggested in the previous anti-patterns. If the relationship is very slight, it
may be necessary to recode the apparently related functionality again in the new class
rather than introduce a high level of coupling by inappropriately deriving one from the
other (whether through inheritance or through composition).
The reduced coupling means that the two classes can in the future change independently
of each other, so facilitating future evolution of the system.

Requirements / Tests for implementation inheritance

The theme of this chapter so far has been: Just because a programming language offers
implementation inheritance does not mean that it should be used at every opportunity!
Implementation inheritance introduces very tight coupling between the superclass and the
subclass and so should only be used where a subclass is also a valid subtype.
So how does one decide when to use implementation inheritance and when not? The
basic test for implementation inheritance is the IsA test. Is the proposed subclass a
superclass in all respects? Do all features of the superclass apply to the subclass, possibly
with some extensions but with minimal need for overriding? Although one cannot know for
sure, it is also worth considering whether future changes to the superclass will be beneficial
and/or not detrimental to the subclass.
A second test is the role test. If the proposed subclass is actually a role of the superclass, it
should not be derived from the superclass.

Using and abusing inheritance (19 Aug 2006, all rights reserved) Chapter 7, Page 13
Underuse of interface inheritance:
Inheritance considered beneficial

One of the first concepts one learns about programming is conditional execution, at first
through the If construct and then the Case construct, and so it is natural to use these
constructs whenever conditional execution is needed. But Ifs and Cases are extremely rigid
and explicit. They need to be carefully thought out and to be carefully rethought when any
changes are introduced.
Undoubtedly If and Case are very important constructs, but with OO there are times
when one can transfer the responsibility for acting appropriately to the objects. So the
problem changes from needing to specify particular behaviour within an If or Case structure
to taking advantage of polymorphism by assigning the responsibility to an appropriate
object.
Chapter 6 provides a simple example of this. In examples 6.2 and 6.3 we see the contrast
between using an extended If..Then..Else structure and polymorphic substitution. A natural
tendency for a non-OO programmer is to use the If..Then..Else structure of example 6.2 and
not the combination of interface inheritance and polymorphic substitution of example 6.3.
So the abuse of interface inheritance is that it is underused and that programs can often
be improved by using it appropriately to replace complex If..Then..Else and Case constructs.

Anti-Pattern 7.5 Conditionals suspect

In situations where classes display consistent types but varying behaviour, this varying
behaviour can often be achieved through subtyping instead of through complex If and Case
structures.

Therefore,

where a program uses extensive conditional evaluation, consider whether it is not


possible to restructure it to take advantage of interface inheritance. This may necessitate
consciously structuring inheritance hierarchies to provide the subtyping needed for
interface inheritance. This allows the programmer to code in terms of an interface instead of
specific implementations.
The benefits of doing this are that the responsibility for taking the appropriate action is
now assigned to a particular object, reducing coupling and so making the system more
flexible and more amenable to future modification.

Chapter 7, Page 14 Object orientation with Delphi (all rights reserved)


Summary of inheritance

Inheritance is often described as an IsA relationship: a subclass IsA superclass. It is also


described as a generalisation-specialisation relationship: the superclass embodies all the
common, general characteristics of its descendants; the subclass adds all those features that
make it special to those it inherits from its ancestor(s). So a subclass is all that the superclass is
plus more.
This leads to the situation that:
– A subclass is an enriched version of its ancestors, and
– A superclass has fewer capabilities than its descendants.
One result of inheritance is the concept of substitution (which, with dynamic binding,
gives polymorphism): since a subclass has all the functionality of its ancestors, an instance of
a subclass can substitute for an instance of any of its ancestors.
A subclass cannot reduce the visibility of any inherited attribute or method. If the data
field FData is public in the superclass, the subclass cannot redefine it as private. However a
subclass can increase visibility. So if FData is private in the superclass, the subclass can
declare it to be public within the subclass. This latter process is referred to as exposing the
superclass’s data or method.
There are two types of inheritance which are implemented similarly. In some ways this is
very convenient, but it is also confusing since they are implemented in similar ways and
little distinction is made between the two.
The more common kind of inheritance is implementation or class inheritance, also
referred to as subclassing. For Gamma et al (1995, p17), class inheritance defines one object’s
implementation in terms of another object’s implementation. It is a mechanism for code and
representation sharing that supports the re-use of data and behaviour.
The second kind is interface or type inheritance, also referred to as subtyping. For
Gamma et al (1995, p17), class inheritance describes when one object can be used in place of
another. This is possible since the child can substitute for the parent. Provided the
programming language has dynamic binding this allows polymorphic operations.
Class inheritance is generally over-used, giving rise to large, inflexible classes that are
error-prone and difficult to maintain and extend, so undermining the benefits of OO. By
contrast, interface inheritance is generally under-used with complex, error-prone and
inflexible conditional structures being used instead. This too undermines the benefits of OO.
Much of this comes about because inheritance is a static relationship created by a
completely rigid structure, making future maintenance potentially difficult should the
relationships need to change and evolve.

Using and abusing inheritance (19 Aug 2006, all rights reserved) Chapter 7, Page 15
Chapter summary

1. Implementation inheritance:
a. IsA relationship
2. Abuses of implementation inheritance:
a. Incomplete subclass
b. Unintended visibility
c. Subclassing roles
d. Construction convenience
3. Interface inheritance:
a. Differences in behaviour
b. Consistent interface
4. Uses of interface inheritance:
a. Replace conditionals

Reference

Gamma, E., Helm, R., Johnson, R. and Vlissides, J. 1995. Design Patterns: Elements of reusable
object-oriented software. Addison-Wesley, Reading, MA.

Problems
Problem 7.1 Study Chapter 7

Identify the appropriate example(s) or section(s) of the chapter to illustrate each comment
made in the summary at the end of chapter 7.

Problem 7.2 Using inheritance appropriately

Reconsider the ‘deep hierarchy’ version of problem 3.3. In what ways does this violate the
requirements for implementation inheritance, and which of the anti-patterns listed in this
chapter does it contravene?

Chapter 7, Page 16 Object orientation with Delphi (all rights reserved)


Problem 7.3 An interface inheritance problem

Reimplement the ‘shallow hierarchy’ version of problem 3.3, taking advantage of concepts,
such as polymorphism and abstract classes, that have been introduced after chapter 3. The
following framework may be helpful in this reimplementation.

First, the driver unit:

1 unit BoxCountU;

2 interface

3 uses
4 Windows, Messages, SysUtils, Variants, classes, Graphics,
5 Controls, Forms, Dialogs, StdCtrls, Buttons, ExtCtrls,
6 ItemCountU;

7 type
8 TfrmCount = class(TForm)
9 // standard RAD declarations

31 private
32 procedure DisplayValues;
33 procedure UpdateCount (APackage: TItemCount;
34 ADirection: integer);
35 end; // end TfrmCount = class(TForm)

36 var
37 frmCount: TfrmCount;

38 implementation

39 uses
40 ItemU, BoxLargeU, BoxSmallU;

41 const
42 Into = 0;
43 OutOf = 1;

44 var
45 ItemCount: TItem;
46 LargeBoxCount: TBoxLarge;
47 SmallBoxCount: TBoxSmall;

48 {$R *.dfm}

49 procedure TfrmCount.btnItemsClick(Sender: TObject);


50 begin
51 UpdateCount (ItemCount, rgpItemDrctn.ItemIndex);
52 end; // end procedure TfrmCount.btnItemsClick

53 procedure TfrmCount.btnLargeBoxesClick(Sender: TObject);

Using and abusing inheritance (19 Aug 2006, all rights reserved) Chapter 7, Page 17
54 begin
55 UpdateCount (LargeBoxCount, rgpLBoxDrctn.ItemIndex);
56 end; // end procedure TfrmCount.btnLargeBoxesClick

57 procedure TfrmCount.btnSmallBoxesClick(Sender: TObject);


58 begin
59 UpdateCount (SmallBoxCount, rgpSBoxDrctn.ItemIndex);
60 end; // end procedure TfrmCount.btnSmallBoxesClick

61 procedure TfrmCount.UpdateCount(APackage: TItemCount;


62 ADirection: integer);
63 begin
64 if ADirection = Into then
65 APackage.Add // polymorphic
66 else if ADirection = Outof then
67 APackage.Subtract; // polymorphic
68 DisplayValues;
69 end; // end procedure TfrmCount.Update

70 procedure TfrmCount.DisplayValues;
71 var
72 TotalCount: integer;
73 begin
74 lblItems.Caption := IntToStr(ItemCount.GetCount);
75 lblLargeBoxes.Caption := IntToStr(LargeBoxCount.GetCount);
76 lblSmallBoxes.Caption := IntToStr(SmallBoxCount.GetCount);
77 TotalCount := ItemCount.GetTotal +
78 LargeBoxCount.GetTotal + SmallBoxCount.GetTotal;
79 lblTotal.Caption := IntToStr(TotalCount);
80 end; // end procedure TfrmCount.DisplayValues

81 procedure TfrmCount.bmbResetClick(Sender: TObject);


82 begin
83 ItemCount.ZeroCount;
84 SmallBoxCount.ZeroCount;
85 LargeBoxCount.ZeroCount;
86 DisplayValues;
87 end; // end procedure TfrmCount.bmResetClick

88 initialization
89 ItemCount := TItem.Create;
90 LargeBoxCount := TBoxLarge.Create;
91 SmallBoxCount := TBoxSmall.Create;

92 end. // end unit BoxCountU

Now the abstract class defining the interface:

1 unit ItemCountU;

2 interface

3 type
4 TItemCount = class(TObject)
5 private

Chapter 7, Page 18 Object orientation with Delphi (all rights reserved)


6 FCount: integer;
7 public
8 procedure Add;
9 procedure Subtract;
10 function GetCount: integer;
11 function GetTotal: integer; virtual; abstract;
12 procedure ZeroCount;
13 end; // end TItemCount = class(TObject)

14 implementation

15 { Implement the concrete method(s), if any }

32 end. // end unit ItemCountU

The concrete classes:

1 unit ItemU;

2 interface

3 uses ItemCountU;

4 type
5 TItem = class(TItemCount)
6 public
7 function GetTotal: integer; override;
8 end; // end TItem = class(TItemCount)

9 implementation

10 { TItem }

11 { Implement the concrete methods(s), if any }

15 end. // end ItemU

1 unit BoxLargeU;

2 interface

3 uses ItemCountU;

4 type
5 TBoxLarge = class(TItemCount)
6 public
7 function GetTotal: integer; override;
8 end; // end TBoxLarge = class(TItemCount)

9 implementation

Using and abusing inheritance (19 Aug 2006, all rights reserved) Chapter 7, Page 19
10 { TBoxLarge }

11 const
12 NoInBox = 12;

13 { Implement the concrete methods(s), if any }

17 end. // end BoxLargeU

1 unit BoxSmallU;

2 interface

3 uses ItemCountU;

4 type
5 TBoxSmall = class(TItemCount)
6 public
7 function GetTotal: integer; override;
8 end; // end TBoxSmall = class(TItemCount)

9 implementation

10 { TBoxSmall }

11 const
12 NoInBox = 4;

13 { Implement the concrete methods(s), if any }

17 end. // end BoxSmallU

Chapter 7, Page 20 Object orientation with Delphi (all rights reserved)

Вам также может понравиться