Designing for Inheritance
Objectives: Designing for Inheritance You will be able to: Refine the analysis-level inheritance hierarchy to increase reuse Discuss how C++ supports inheritance Define polymorphism and describe how it is supported in C++ Differentiate between static and dynamic binding Use virtual functions to request dynamic binding Determine the appropriate level of access for inherited data and function members
Inheritance Hierarchies During analysis, inheritance hierarchies among key abstractions (i.e., classes) are established During design, the inheritance hierarchies are refined to: Increase reuse Incorporate implementation classes Incorporate available library classes
Refining the Inheritance Hierarchy Analysis diagrams are reviewed to identify commonality of Attributes, Operations, and/or Associations New superclasses are defined that contain common elements This reduces the amount of code to be written and enforces uniformity, i.e., the same item cannot be handled differently in two different classes if the two classes inherit it from a common superclass
Example: Refining the Hierarchy Mortgage interestRate getPayment( ) Customer SavingAccount getBalance( ) 1..* granted to is owned by 0..* Analysis-Level Class Diagram
Example: Refining the Hierarchy InterestBearingItem Association with Customer was moved to superclass interestRate is inherited by both subclasses and must be handled identically Both getPayment and getBalance require calculation of the amount of interest which is performed by the inherited getRate method Customer interestRate 1..* 1..* 0..* 0..* Mortgage SavingsAccount getPayment getBalance Design-Level Class Diagram
C++ Support for Inheritance C++ provides direct language-level support for inheritance of attributes and operations Inheritance terminology is “Base” class instead of superclass “Derived” class instead of subclass
Operations Are Inherited from Base Classes in C++ class BankAcct { public: void deposit (); }; class SavingsAcct : public BankAcct { void getBalance (); void client () { SavingsAcct myAcct; myAcct.getBalance (); // derived myAcct.deposit (); // base A client of a derived class can invoke public members of derived and base classes Operations declared in a base class need not be repeated in the derived class
Attributes Are Inherited from Base Classes in C++ Objects x aBase z aDerived class base { private: int x; }; class derived : public base { int z; Attributes declared in a base class need not be repeated in the derived class
Polymorphism Polymorphism is the ability to hide many different implementations behind a single interface Clients can invoke an object’s operations without knowing its type Clients can be implemented “generically” to invoke an object’s operation without knowing the object’s type If objects are added that support the same operation, the client need not be modified to handle the new object Polymorphism allows clients to manipulate objects in terms of their common superclass
Example of Polymorphism if animal = “Lion” then Lion.draw() else if animal = “Tiger” then Tiger.draw() end Without Polymorphism With Polymorphism Animal.draw()
Polymorphism and C++ Polymorphism is an advantage of inheritance realized during implementation C++ provides support for polymorphism through Dynamic (or late) binding Virtual functions The designer must explicitly permit polymorphism through proper use of C++ virtual member functions virtual void draw();
Static Versus Dynamic Binding Normally the particular method to be executed as a result of a function call is known at compile time. This is called static (or early) binding The compiler replaces the function call with code telling the program which address to jump to in order to find that function With polymorphism, the particular type of object for which a method is to be invoked is not known until run time The compiler cannot provide the address at compile time The method is selected by the program as it is running This is known as late binding or dynamic linking
Inheritance and Destructors If a destructor is not virtual then a delete through a base class pointer will call the wrong destructor if the object pointed to is of the derived class
Inheritance and Destructors class Ball { public: Ball(); ~Ball(); } class Baseball : public Ball { public: Baseball(); ~Baseball(); } main() { Ball *myBall; myBall = new Baseball(); OK -- Baseball constructor called delete myBall; not OK -- Ball destructor called }
Virtual and Performance Every time the virtual function is called, the correct function to be invoked is determined by examining the virtual table Few instruction sets per call Functions cannot be inlined Compiler does not know what to inline Most impact on small functions Time for function call is a significant percentage of the function execution time Bottom Line: If the use of virtual bases and virtual functions makes the design cleaner, and the code easier to understand then it is probably worth it
Design Process for Polymorphism At design time the developer must: Examine inheritance hierarchies and determine which operations should be polymorphic Update class diagrams to indicate each class and/or subclass that must provide a method for a given operation Declare all polymorphic operations to be virtual in the base class that defines them
Abstract Classes An abstract class is one for which no instances may be created Abstract classes must have at least one subclass to be useful Abstract All objects are either lions or tigers; there are no direct instances of Animal
Designing Abstract Classes Abstract classes are designed differently from concrete classes With abstract classes the designer assumes that subclasses will add to its structure and behavior The abstract class need not provide methods for each operation it defines The abstract class must provide the protocol for all polymorphic operations
Example: Abstract Classes and Protocols The Animal does not need to provide a method for draw() Methods must be provided by Lion and Tiger for draw() and these methods must conform to the protocol defined in Animal
C++ and Abstract Classes C++ allows a developer to assert that an abstract class’s method cannot be invoked directly by initializing its declaration to zero Such a method is called a pure virtual function C++ prohibits the creation of instances of classes containing pure virtual functions class Animal { ... virtual void draw() = 0; }; Ensures that no instances of Animal can be created
Abstract Classes and Inheritance There are 3 important function design considerations involved here: To provide a function’s interface only to derived classes: Use a pure virtual function To provide a function’s interface and default behavior to derived classes: Use a standard (non-pure) virtual function (with code that can be overridden) To provide a function’s interface and mandatory behavior to derived classes: Use a non-virtual function (which is NOT designed to be overridden by subclasses)
Public Derivation Versus Private Derivation Subclasses are implemented in C++ using public derivation With public derivation, the public interface of the base class remains public in the derived class Objects of the derived class can access all public features of the base class Member functions of the derived class have access to all inherited protected and public attributes and operations
Public Derivation Versus Private Derivation C++ also permits private derivation in which the public interface of the base class becomes private in the derived class Objects of the derived class cannot access public features of the base class Member functions of the derived class still have access to all public and protected attributes and operations Private derivation is helpful in reusing implementations - it is not true inheritance and is not used to implement subclasses Means “is implemented in terms of”
Liskov Substitution Principle If for each object O1 of type S there is an object O2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when O1 is substituted for O2 then S is a subtype of T Less Formally: You can always pass a pointer or reference to a derived class to a function that expects a pointer or reference to a base class These rules represent the ISA style of programming
ISA Style of Programming Do these classes follow the ISA style of programming?
ISA Style of Programming The classes do NOT follow the ISA style of programming A Stack needs some of the behavior of a List but not all of the behavior If a method expects a List, then the operation insert(position) should be successful If the method is passed a Stack, then the insert(position) will fail A subclass (ISA style derived class) must have NO more constraints than its superclass
Factoring Factoring can sometimes be used to fix the problem Cannot be used if the List class cannot be changed
Delegation Delegation can also be used to fix the problem void Stack::push(Item I) { body.insertTop(I); }; const Item Stack::pop() { return body.removeTop();
Private Inheritance Private inheritance is sometimes used to circumvent ISA style problems void Stack::push(Item I) { insertTop(I); }; const Item Stack::pop() { return removeTop(); push() and pop() can access methods of List but instances of Stack cannot
Multiple Inheritance With multiple inheritance, a subclass inherits from more than one superclass
C++ Support for Multiple Inheritance Much more complexity associated with designing multiple inheritance Often overused Two problems that must be resolved: Name clashes or collisions Repeated inheritance
Name Collisions Name collisions result when two or more superclasses define the same attribute or operation InsurableItem and InterestBearingItem both have attributes named presentValue Asset InsurableItem presentValue InterestBearing BankAccount A BankAccount object wants to print the presentValue Which one is printed ?
Resolving Name Collisions This ambiguity can be resolved by fully qualifying the name to indicate the source of the declaration InsurableItem::presentValue OR InterestBearingItem:: presentValue
Example: Multiple Inheritance The more complex the hierarchy, the more difficult to detect name collisions
Example: C++ Declarations class Asset . . . class InsurableItem : public Asset class InterestBearing : public Asset class BankAccount : public InsurableItem, public InterestBearing . . . class RealEstate : public Asset, public InsurableItem . . . class Security : public InterestBearing . . . class SavingsAccount : public BankAccount . . . class CheckingAccount : public BankAccount . . . class Stock : public Security . . . class Bond : public Security . . .
Repeated Inheritance Another problem associated with multiple inheritance is repeated inheritance. Consider the following example:
Repeated Inheritance Note the distinctive diamond shape of the inheritance hierarchy This indicates that the same base class is being inherited by a derived class more than once. For example, ScrollingWindowWithDialogBox is inheriting Window more than once With repeated inheritance, two or more peer superclasses share a common superclass How many copies of the attributes of Window are included in instances of ScrollingWindowWithDialogBox?
Virtual Base Classes In this case, the ScrollingWindowWithDialogBox needs only one copy of the Window instance variables To ensure that one copy is inherited, the common base class is declared virtual when it is being derived into intermediate base classes All intermediate classes must derive from the common base class in a virtual fashion The single copy that is inherited is considered to be shared by the multiple derivation paths Use of the scope resolution operator to refer to shared features inherited from the common base class is not required with virtual derivation
Example: Virtual Base Class Window is the base class Each window has one x and one y which indicate the point of origin The Window::paint member function paints the basic window class Window { public: ... virtual void paint( ) { // paint window stuff only } protected: Point x, y; // origin } ;
Example: Intermediate Derived Classes The intermediate derived classes derive from Window in a virtual fashion Each derived class inherits one x and one y from the base class and the Window::paint member function class WindowWithDialogBox : public virtual Window { public: … void dialogBoxPaint( ); // paint only dialog box virtual void paint( ); // invoke Window::paint // and // paint dialog box }; class WindowWithScrollBar : public virtual Window { public: ... void scrollBarPaint( ); // paint only scrollbar virtual void paint( ); // invoke Window::paint and // paint scrollbar } ;
Example: Repeated Inheritance Derived class ScrollableWindowWithDialogBox inherits one x and one y because both parent classes were derived virtually from Window Note that this class does not include the keyword virtual in deriving from its parent classes class ScrollableWindowWithDialogBox : public WindowWithDialogBox , public WindowWithScrollBar { public: ... virtual void paint( ); } ;
Repeated Inheritance and Member Functions A common mistake in designing a lowest-level derived class is to invoke the base class’s common function more than once For example, suppose we code the lowest-level paint function as follows: virtual void ScrollableWindowWithDialogBox::paint( ) { WindowWithDialogBox ::paint( ) ; WindowWithScrollBar ::paint( ) ; } Then the Window::paint function would be invoked twice, once from WindowWithDialogBox::paint and once from WindowWithScrollBar::paint This could simply be a waste of time or could (in some systems) corrupt the screen image
Repeated Inheritance and Member Functions In the following code, that mistake has been avoided virtual void ScrollableWindowWithDialogBox::paint( ) { Window::paint( ) ; // invoke base class paint only once ! WindowWithDialogBox::dialogBoxPaint( ); // then paint the dialog box WindowWithScrollBar::scrollBarPaint( ); // then paint the scrollbar }
Multiple Inheritance Multiple inheritance is conceptually straightforward and is needed to accurately model many real-world problems Novice designers tend to overuse multiple inheritance, e.g., use multiple inheritance when aggregation would do In practice, multiple inheritance is a complex design problem and may lead to implementation difficulties, including name clashes and repeated inheritance Use multiple inheritance only when needed, and always with caution !
Exercise Discuss inheritance design decisions for the problem being developed
Summary: Designing for Inheritance Inheritance defines a relationship among classes where one class shares the structure and/or behavior of one or more classes During design, new superclasses are added to class diagrams to contain common attributes, operations, and/or associations C++ provides direct language-level support for inheritance of attributes and operations Polymorphism is the ability to hide many different implementations behind a single interface C++ provides support for polymorphism through Dynamic (or late) binding Virtual functions
Summary: Designing for Inheritance Normally the particular method to be executed as a result of a function call is known at compile time. This is called static (or early) binding With dynamic (or late) binding, the method to be executed as a result of a function call is selected by the program as it is running In C++, if a function is not declared virtual, static binding is used An abstract class is one for which no instances may be created With abstract classes the designer assumes that subclasses will add to its structure and behavior C++ allows a developer to assert that an abstract class’s method cannot be invoked directly by initializing its declaration to zero. Such a method is called a pure virtual function
Summary: Designing for Inheritance Public derivation is used to implement subclasses Private derivation is sometimes used to reuse implementations With multiple inheritance, a subclass inherits from more than one superclass Two problems with multiple inheritance that must be resolved: Name clashes or collisions Repeated inheritance