Inheritance and Dynamic Binding: idioms and common patterns

Steven Zeil

Last modified: Mar 11, 2014

1. Inheritance Captures Variant Behavior

Why bother with inheritance & dynamic binding at all?

Because it offers a convenient mechanism for capturing variant behaviors among different groups of objects.

  string render(int width)
  Value* clone()

But to actually store a numeric value, we need a data member to hold a number (and a function via which we can retrieve it, though we’ll ignore that for the moment). Similarly, we can expect that, to store a string value, we would need a data member to store the string.

We will assume, therefore, that

A pre-OO Approach to Variant behavior

 class Value {
 public:
   enum ValueKind {Numeric, String, Error};
   Value (double dv);
   Value (string sv);
   Value ();
 
   ValueKind kind;
   double d;
   string s;
 
   string render(int width) const;
 }

A pre-OO Approach to Variant behavior

 class Value {
 public:
   enum ValueKind {Numeric, String, Error};
   Value (double dv);
   Value (string sv);
   Value ();
 
   ValueKind kind;
   double d;
   string s;
 
   string render(int width) const;
 }

Multi-Way Branching

 string Value::render(int width) const {
   switch (kind) {
     case Numeric: {
       string buffer;
       ostringstream out (buffer);
       out << dv.d;
       return buffer.substr(0,width);
       }
     case String:
       return sv.s.substr(0.width);
     case Error:
       return string("** error **").substr(0.width);
   }
 }

Variant Behavior under OO

valuerender.h

Now, compare that with the OO approach.

Variants are Separated

valuerender.cpp

Here’s the OO take on the same render function.

Summary

We use inheritance in our programming designs whenever we find ourselves looking at objects that

Conversely, if we don’t see that kind of variant behavior, we probably have no need for inheritance.

2. Using Inheritance

We’ll look at 3 idioms describing good ways to use inheritance.

2.1 Specialization

When inheritance is used for specialization,

The new class may therefore be substituted for a value of the parent.

This is, in many ways, the “classical” view of inheritance.

Recognizing Specialization

2.2 Specification

Inheritance for specification takes place when

Defining Protocols

A protocol is a set of messages (functions) that can be used together to accomplish some desired task.

Recognizing the Specification Idiom of Inheritance

Example: Varying Data Structures

A common requirement in many libraries is to provide different data structures for the same abstraction.

libg++

Working with a Specialized Protocol

genset.cpp

A programmer could write code, like the code shown here, that could work an any set.

Abstract Base Classes

Adding to a Set Subclass

If we are working with libg++ This is OK:

void foo (Set& s, int x)
{
   s.add(x);
   cout << x << " has been added." << endl;
}
 
int main ()
{
   BSTSet s;
   foo (s, 23);
    ⋮

Adding to a General Set

But what should happen here?

 void foo (Set& s, int x)
 {
     s.add(x);
     cout << x << " has been added."   << endl;
 }
 
 int main ()
 {
     Set s;
     foo (s, 23);
       ⋮

How Do We Prevent This?

 void foo (Set& s, int x)
 {
     s.add(x);
     cout << x << " has been added."   << endl;
 }
 
 int main ()
 {
     Set s;
     foo (s, 23);
       ⋮

Abstract Member Functions

class Set {
      ⋮
    virtual Set& add (int) = 0;
      ⋮
};
   

Abstract Classes

An abstract class in C++ is any class that

“Abstract classes” are also known as pure virtual classes.

Set as an Abstract Class

Set in libg++ is a good example of a class that should be abstract.

Limitations of Abstract Classes

Abstract classes carry some limitations, designed to make sure we use them in a safe manner.

class Set {
   ⋮
  virtual Set& add (int) = 0;
   ⋮
};
  ⋮
void foo (Set& s, int x) // OK
  ⋮
 
int main () {
   Set s;  // error!
   foo (s, 23);
     ⋮

Abstract Classes & Specification

value.h

expression.h

2.3 Extension

In this style of inheritance, a limited number of “new” abilities is grafted onto an otherwise unchanged superclass.


 class FlashingString: public StringValue {
   bool _flash;
 public:
   FlashingString (std::string);
   void flash();
   void stopFlashing();
 };

Are Extensions OK?

Mixins

A mixin is a class that makes little sense by itself, but provides a specialized capability when used as a base class.

Mixin Example: Noisy

Noisy is a mixin I use when debugging.

noisy.h

noisy.cpp

Using Noisy

 class TreeSet
   : public Set, public Noisy
 {
      ⋮

Mixin Example: checking for memory leaks

counted.h

counted.cpp

3. The Observer Pattern

observer.h

observable.h

observable.cpp

3.1 Applications of Observer

Example: Propagating Changes in a Spreadsheet

Anyone who has used a spreadsheet has observed the way that, when one cell changes value, all the cells that mention that first cell in their formulas change, then all the cells the mention those cells change, and so on, in a characteristic “ripple” effect until all the effects of the original change have played out.

There are several ways to program that effect. One of the more elegant is to use the Observer/Observable pattern.

Cells Observe Each Other


class Cell: public Observable, Observer
{
public:

The idea is that cells will observe one another.

Changing a Cell Formula

putFormula.cpp

Here’s the code that’s actually invoked to change the expression stored in a cell.

Evaluating a Cell’s Formula

evaluateFormula.cpp

Eventually the spreadsheets calls this function on our recently changed cell.

Notifying a Cell’s Observers

void Cell::notify (Observable* changedCell)
{
  theSheet.cellRequiresEvaluation (this);
}  

What does an observing cell do when it is notified? It tells the spreadsheet that it needs to be re-evaluated.

Eventually the propagation trickles to an end, as we eventually re-evaluate cells that either do not change value or that are not themselves mentioned in the formulae of any other cells.

Example: Observer and GUIs

A spreadsheet GUI contains a rectangular array of CellViews. Each CellView observes one Cell

Scrolling the Spreadsheet

Not every cell will have a CellView observer.

Model-View-Controller (MVC) Pattern

MVC Interactions

How do we actually accomplish this?