• No results found

Inheritance (“is a” relationships)

Inheritance is the complex idea that one class is a specialization of another class.

727

Inheritance is perhaps the most distinctive attribute of object-oriented

728

programming, and it should be used sparingly and with great caution. A great

729

many of the problems in modern programming arise from overly enthusiastic use

730

of inheritance.

731

The purpose of inheritance is to create simpler code by defining a base class that

732

specifies common elements of two or more derived classes. The common

733

elements can be routine interfaces, implementations, data members, or data

734

types.

735

When you decide to use inheritance, you have to make several decisions:

736

● For each member routine, will the routine be visible to derived classes? Will

737

it have a default implementation? Will the default implementation be

738

overridable?

739

● For each data member (including variables, named constants, enumerations,

740

and so on), will the data member be visible to derived classes?

741

The following subsections explain the ins and outs of making these decisions.

742

Implement “is a” through public inheritance

743

When a programmer decides to create a new class by inheriting from an existing

744

class, that programmer is saying that the new class “is a” more specialized

745

version of the older class. The base class sets expectations about how the derived

746

class will operate (Meyers 1998).

747

If the derived class isn’t going to adhere completely to the same interface 748

contract defined by the base class, inheritance is not the right implementation

749

technique. Consider containment or making a change further up the inheritance

750

hierarchy.

751

The single most important rule in object- oriented programming with C++ is this: public inheritance means “isa.” Commit this rule to memory.

Design and document for inheritance or prohibit it

752

Inheritance adds complexity to a program, and, as such, it is a dangerous

753

technique. As Java guru Joshua Bloch says, “design and document for

754

inheritance, or prohibit it.” If a class isn’t designed to be inherited from, make its

755

members non-virtual in C++, final in Java, or non overridable in Visual Basic so 756

that you can’t inherit from it.

757

Adhere to the Liskov Substitution Principle

758

In one of object-oriented programming’s seminal papers, Barbara Liskov argued

759

that you shouldn’t inherit from a base class unless the derived class truly “is a”

760

more specific version of the base class (Liskov 1988). Andy Hunt and Dave

761

Thomas suggest a good litmus test for this: “Subclasses must be usable through

762

the base class interface without the need for the user to know the difference”

763

(Hunt and Thomas 2000).

764

In other words, all the routines defined in the base class should mean the same

765

thing when they’re used in each of the derived classes.

766

If you have a base class of Account, and derived classes of CheckingAccount, 767

SavingsAccount, and AutoLoanAccount, a programmer should be able to invoke 768

any of the routines derived from Account on any of Account’s subtypes without 769

caring about which subtype a specific account object is.

770

If a program has been written so that the Liskov Substitution Principle is true,

771

inheritance is a powerful tool for reducing complexity because a programmer can

772

focus on the generic attributes of an object without worrying about the details. If,

773

a programmer must be constantly thinking about semantic differences in subclass

774

implementations, then inheritance is increasing complexity rather than reducing

775

it. Suppose a programmer has to think, “If I call the InterestRate() routine on 776

CheckingAccount or SavingsAccount, it returns the interest the bank pays, but if I 777

call InterestRate() on AutoLoanAccount I have to change the sign because it 778

returns the interest the consumer pays to the bank.” According to Liskov, the

779

InterestRate() routine should not be inherited because its semantics aren’t the 780

same for all derived classes.

781

Be sure to inherit only what you want to inherit

782

A derived class can inherit member routine interfaces, implementations, or both.

783

Table 6-1 shows the variations of how routines can be implemented and

784

overridden.

Table 6-1. Variations on inherited routines

786

Overridable Not Overridable

Implementation: Default Provided

Overridable Routine Non-Overridable Routine

Implementation: No default provided

Abstract Overridable Routine

Not used (doesn’t make sense to leave a routine undefined and not allow it to be overridden) As the table suggests, inherited routines come in three basic flavors:

787

● An abstract overridable routine means that the derived class inherits the 788

routine’s interface but not its implementation.

789

● An overridable routine means that the derived class inherits the routine’s 790

interface and a default implementation, and it is allowed to override the

791

default implementation.

792

● A non-overridable routine means that the derived class inherits the routine’s 793

interface and its default implementation, and it is not allowed to override the

794

routine’s implementation.

795

When you choose to implement a new class through inheritance, think through

796

the kind of inheritance you want for each member routine. Beware of inheriting

797

implementation just because you’re inheriting an interface, and beware of

798

inheriting an interface just because you want to inherit an implementation.

799

Don’t “override” a non-overridable member function

800

Both C++ and Java allow a programmer to override a non-overridable member

801

routine—kind of. If a function is private in the base class, a derived class can 802

create a function with the same name. To the programmer reading the code in the

803

derived class, such a function can create confusion because it looks like it should

804

by polymorphic, but it isn’t; it just has the same name. Another way to state this

805

guideline is, Don’t reuse names of non-overridable base-class routines in derived

806

classes.

807

Move common interfaces, data, and behavior as high as possible in the

808

inheritance tree

809

The higher you move interfaces, data, and behavior, the more easily derived

810

classes can use them. How high is too high? Let abstraction be your guide. If 811

you find that moving a routine higher would break the higher object’s

812

abstraction, don’t do it.

813

Be suspicious of classes of which there is only one instance

814

A single instance might indicate that the design confuses objects with classes.

815

Consider whether you could just create an object instead of a new class. Can the

variation of the derived class be represented in data rather than as a distinct

817

class?

818

Be suspicious of base classes of which there is only one derived class

819

When I see a base class that has only one derived class, I suspect that some

820

programmer has been “designing ahead”—trying to anticipate future needs,

821

usually without fully understanding what those future needs are. The best way to

822

prepare for future work is not to design extra layers of base classes that “might

823

be needed someday,” it’s to make current work as clear, straightforward, and

824

simple as possible. That means not creating any more inheritance structure than

825

is absolutely necessary.

826

Be suspicious of classes that override a routine and do nothing inside the

827

derived routine

828

This typically indicates an error in the design of the base class. For instance,

829

suppose you have a class Cat and a routine Scratch() and suppose that you 830

eventually find out that some cats are declawed and can’t scratch. You might be

831

tempted to create a class derived from Cat named ScratchlessCat and override 832

the Scratch() routine to do nothing. There are several problems with this 833

approach:

834

● It violates the abstraction (interface contract) presented in the Cat class by 835

changing the semantics of its interface.

836

● This approach quickly gets out of control when you extend it to other

837

derived classes. What happens when you find a cat without a tail? Or a cat

838

that doesn’t catch mice? Or a cat that doesn’t drink milk? Eventually you’ll

839

end up with derived classes like ScratchlessTaillessMicelessMilklessCat. 840

● Over time, this approach gives rise to code that’s confusing to maintain

841

because the interfaces and behavior of the ancestor classes imply little or

842

nothing about the behavior of their descendents.

843

The place to fix this problem is not in the base class, but in the original Cat class. 844

Create a Claws class and contain that within the Cats class, or build a constructor 845

for the class that includes whether the cat scratches. The root problem was the

846

assumption that all cats scratch, so fix that problem at the source, rather than just

847

bandaging it at the destination.

848

Avoid deep inheritance trees

849

Object oriented programming provides a large number of techniques for

850

managing complexity. But every powerful tool has its hazards, and some object-

851

oriented techniques have a tendency to increase complexity rather than reduce it.

852

In his excellent book Object-Oriented Design Heuristics, Arthur Riel suggests 853

limiting inheritance hierarchies to a maximum of six levels (1996). Riel bases his

recommendation on the “magic number 7±2,” but I think that’s grossly

855

optimistic. In my experience most people have trouble juggling more than two or

856

three levels of inheritance in their brains at once. The “magic number 7±2” is

857

probably better applied as a limit to the total number of subclasses of a base 858

class rather than the number of levels in an inheritance tree.

859

Deep inheritance trees have been found to be significantly associated with

860

increased fault rates (Basili, Briand, and Melo 1996). Anyone who has ever tried

861

to debug a complex inheritance hierarchy knows why.

862

Deep inheritance trees increase complexity, which is exactly the opposite of

863

what inheritance should be used to accomplish. Keep the primary technical

864

mission in mind. Make sure you’re using inheritance to minimize complexity. 865

Prefer inheritance to extensive type checking

866

Frequently repeated case statements sometimes suggest that inheritance might be 867

a better design choice, although this is not always true. Here is a classic example

868

of code that cries out for a more object-oriented approach:

869

C++ Example of a Case Statement That Probably Should be Replaced

870 by Inheritance 871 switch ( shape.type ) { 872 case Shape_Circle: 873 shape.DrawCircle(); 874 break; 875 case Shape_Square: 876 shape.DrawSquare(); 877 break; 878 ... 879 } 880

In this example, the calls to shape.DrawCircle() and shape.DrawSquare() should 881

be replaced by a single routine named shape.Draw(), which can be called 882

regardless of whether the shape is a circle or a square.

883

On the other hand, sometimes case statements are used to separate truly different 884

kinds of objects or behavior. Here is an example of a case statement that is 885

appropriate in an object-oriented program:

886

C++ Example of a Case Statement That Probably Should not be

887 Replaced by Inheritance 888 switch ( ui.Command() ) { 889 case Command_OpenFile: 890 OpenFile(); 891 break; 892

case Command_Print: 893 Print(); 894 break; 895 case Command_Save: 896 Save(); 897 break; 898 case Command_Exit: 899 ShutDown(); 900 break; 901 ... 902 } 903

In this case, it would be possible to create a base class with derived classes and a

904

polymorphic DoCommand() routine for each command. But the meaning of 905

DoCommand() would be so diluted as to be meaningless, and the case statement 906

is the more understandable solution.

907

Avoid using a base class’s protected data in a derived class (or make that

908

data private instead of protected in the first place)

909

As Joshua Bloch says, “Inheritance breaks encapsulation” (2001). When you

910

inherit from an object, you obtain privileged access to that object’s protected

911

routines and data. If the derived class really needs access to the base class’s

912

attributes, provide protected accessor functions instead.

913

Multiple Inheritance