Object Oriented Programming/Inheritance

From Wikibooks, open books for an open world
Jump to navigation Jump to search

Inheritance[edit]

In many books, inheritance and OOP are made to seem synonymous, so it may seem strange that we deferred this discussion so far. This is a reflection of the diminished role of inheritance over time. In fact, one of the primary distinctions between Classic and Modern OOP lies in the usage of inheritance. As the old adage goes, if all you have is a hammer, then everything looks like a nail. And so it happened that often times, inheritance was the only tool available to the erstwhile OOP programmer, and so every concept under the sun (or at least the ghostly glow of a CRT) was crammed into inheritance. This lack of conceptual integrity and separation of concerns led to over-intimate dependencies and many difficulties. In some languages, programmer technique evolved to make the concepts clearer using the same limited language functions, while other languages explicitly developed features to address these concerns. Because you are almost certain to be exposed to some of this misguided advice at some point in your OOP learning, we'll try and explain some of the problems to you. However, much of the discussion will go in the sections where they properly belong!

First off, though, what is inheritance? Well, just like when your grandma dies and you inherit that gawd-awful ugly lamp, inheritance in OOP grants the child-class (or derived-class) all of the attributes of the parent (or super) class. So, for example (in C++):

class Parent
{
public:
   int f() {return 10;}
};
class Child : public Parent
{
// see, nothing here!  But wait, we inherited the function f()!
};
Child c;
int result = c.f();  // huh?  Oh, f() was inherited from the parent!

Is-A vs Has-A Moley Baloney[edit]

The most over-used and rather worthless discussion on inheritance that you will see revolves around the "Is-A vs Has-A" discussion. For example, a car is-a vehicle but has-a steering wheel. The idea these authors are chasing is that your car class should inherit your vehicle class and have a steering wheel as a member. You might also run across examples with shapes, where a Rectangle is-a Shape. The point is, once again, abstraction. The goal is to identify operations that operate only on the abstract notion of vehicle or shape and then write that code only once. Then, through the magic of inheritance, you can pass in cars or rectangles or what-have-you to the generic code, and it will work, since the derived classes are everything the parent classes are, "plus more".

The problem here is that inheritance is mixing together several things: you inherit "typeness", interface, and implementation all at the same time. However, all of the examples focus on interface while talking about "typeness". The abstract code doesn't care that a car "is-a" vehicle, just that the objects respond to a certain set of functions, or interface. In fact, if you want to give your chair class accelerate(), brake(), turn_left() and turn_right() methods, shouldn't the abstract code be able to work on chairs then? Well, of course, but that doesn't make chairs vehicles.

The kinds of solutions proposed in these "is-a" discussions have thus been mostly replaced by so-called interface programming and template programming. Since template programming offers the loosest coupling, it focused some attention on the syntactic-semantic confusion. Just because you have functions with the proper names, does that mean that they do what you think they do? If a chair class has accelerate(), brake() and other vehicle type methods, does it make sense to have generic vehicle code work on this chair? This led to more codifying of the assumptions of generic code: say, brake(INFINITY) ==> STOPPED. This implies that brake(x) != accelerate(-x). So chair may make a better vehicle than the Starship Enterprise!

Delegation and "Dynamic Inheritance"[edit]

You will see more about delegation later in the section on message passing, but we wanted to mention that many problems solved by delegation are often tackled by inheritance in languages that don't offer a delegation feature. It's easier to show an example than explain, so in short, here's what delegation looks like:

interface I
{
   int f();
}
class A implements I
{
   // A member variable that actually services all requests for
   //  calls to the I interface.
   delegate I private_i;

   A(I target)
   {
       private_i = target;
   }
}
class B implements I
{
   int f() { return 1; }
}
B b;  // create a B object to be the target of A's delegation
A a(b);  // Create an A object, passing in b

int foo = a.f();  // This call to f() is delegated to b.f(), causing 1 to be returned.

Inheritance accomplishes a similar feat

class B
{
   int f() { return 1; }
}
class A inherits B
{
}
A a;

int foo = a.f();  // returns 1 just like in the delegation example.

However, the delegate can change over time, whereas base classes cannot. As a result, sometimes delegation is referred to as "dynamic inheritance".

Multiple Inheritance[edit]

Multiple inheritance is where an object inherits its properties from many different classes. For example, a house is both a "building" and a "place to sleep in".

In multiple inheritance, an object inherits its properties from many objects (its parents or base classes). Therefore, we need to set up some classes, which we will use C++ for:

class Building
{
  int size;
  int purpose;
  int price;
};
class PlaceToSleepIn
{
  int type; // tent, house, dorm, etc.
  bool pleasant_to_live_in; // true or false
};

In C++, to inherit all the types, one uses the : operator, like so:

class House : public Building, public PlaceToSleepIn
{
  // all of the Building and PlaceToSleepIn variables are here!
  int rooms; // the number of rooms
};

Problems with Inheritance[edit]

One problem of Inheritance is that users of predefined classes may not know the location of the abstracted data. Therefore, it may not be easy for them to obtain it. Most of the problems with inheritance is due to design fragility or issues with the way a programming language implements it.

A normal issue in dealing with inheritance in large programs is called Fragile Base Class. It arises when subclasses of the base class make assumptions about the property (the definition of the attribute) that are outside the contract provided by the base class.

An example could be of an attribute that was declared in the base class as byte but later turns out to need the range of word. Changing this to word will necessitate at least recompiling all sub-classes but that might well not be enough because the sub-classes might have relied on the type of this attribute so that the source code also will need to be changed.

This intrinsic knowledge of the implementor about a given implementation is a problem in general programming but one that OOP/OOD does attempt to solve by offering greater opportunities for better programming practices. But as anything else with code it is up for the programmer to make use of it as to be as transparent as possible as to enable other to take full advantage of its code and so of the property of inheritance.