Inheritance was defined in Chapter 1 as a system in which child classes inherit attributes and behaviors from a parent class. However, there is more to inheritance, and in this chapter we explore inheritance in greater detail.
Chapter 1 states that you can determine an inheritance relationship by following a simple rule: If you can say that Class B is a Class A, then this relationship is a good candidate for inheritance.
Let’s revisit the mammal example used in Chapter 1. Let’s consider a Dog
class. A dog has several behaviors that make it distinctly a dog, as opposed to a cat. For this example, let’s specify two: A dog barks, and a dog pants. So, we can create a Dog
class that has these two behaviors, along with two attributes (see Figure 7.1).
Now let’s say that you want to create a GoldenRetriever
class. You could create a brand-new class that contains the same behaviors that the Dog
class has. However, we could make the following, and quite reasonable, conclusion: A Golden Retriever is-a dog. Because of this relationship, we can inherit the attributes and behaviors from Dog
and use it in our new GoldenRetriever
class (see Figure 7.2).
The GoldenRetriever
class now contains its own behaviors as well as all the more general behaviors of a dog. This provides us with some significant benefits. First, when we wrote the GoldenRetriever
class, we did not have to reinvent part of the wheel by rewriting the bark
and pant
methods. Not only does this save some design and coding time, but it saves testing and maintenance time as well. The bark
and pant
methods are written only once and, assuming that they were properly tested when the Dog
class was written, they do not need to be heavily tested again; but it does need to be retested because there are new interfaces, and so on.
Now let’s take full advantage of our inheritance structure and create a second class under the Dog
class: a class called LhasaApso
. Whereas retrievers were bred for retrieving, Lhasa Apsos were bred for use as guard dogs. These dogs are not attack dogs; they have acute senses, and when they sense something unusual, they start barking. So, we can create our LhasaApso
class and inherit from the Dog
class just as we did with the GoldenRetriever
class (see Figure 7.3).
Another primary advantage of inheritance is that the code for bark()
and pant()
is in a single place. Let’s say there is a need to change the code in the bark()
method. When you change it in the Dog
class, you do not need to change it in the LhasaApso
class and the GoldenRetriever
class.
Do you see a problem here? At this level, the inheritance model appears to work very well. However, can you be certain that all dogs have the behavior contained in the Dog
class?
In his book Effective C++, Scott Meyers gives a great example of a dilemma with design using inheritance. Consider a class for a bird. One of the most recognizable characteristics of a bird is, of course, that it can fly. So, we create a class called Bird
with a fly
method. You should immediately understand the problem. What do we do with a penguin, or an ostrich? They are birds, yet they can’t fly. You could override the behavior locally, but the method would still be called fly
. And it would not make sense to have a method called fly
for a bird that does not fly but only waddles, runs, or swims.
This leads to some potentially significant problems. For example, if a penguin has a fly
method, the penguin might understandably decide to test it out. However, if the fly
method was in fact overridden and the behavior to fly did not exist, the penguin would be in for a major surprise when the fly
method is invoked after jumping over a cliff. Imagine the penguin’s chagrin when the call to the fly
method results in waddling instead of flight. In this situation, waddling doesn’t cut it. Just imagine if code such as this ever found its way into a spacecraft’s guidance system.
In our dog example, we have designed the class so that all dogs have the ability to bark. However, some dogs do not bark. The Basenji breed is a barkless dog. Although these dogs do not bark, they do yodel. So should we reevaluate our design? What would this design look like? Figure 7.4 is an example that shows a more correct way to model the hierarchy of the Dog
class.
Consider the object model of the Dog
class hierarchy. We started with a single class, called Dog
, and we factored out some of the commonality between various breeds of dogs. This concept, sometimes called generalization-specialization, is yet another important consideration when using inheritance. The idea is that as you make your way down the inheritance tree, things get more specific. The most general case is at the top of the tree. In our Dog
inheritance tree, the class Dog
is at the top and is the most general category. The various breeds—the GoldenRetriever
, LhasaApso
, and Basenji
classes—are the most specific. The idea of inheritance is to go from the general to the specific by factoring out commonality.
In the Dog
inheritance model, we started factoring out common behavior by understanding that although a retriever has some different behavior from that of a LhasaApso
, the breeds do share some common behaviors—for example, they both pant and bark. Then we realized that all dogs do not bark—some yodel. Thus, we had to factor out the barking behavior into a separate BarkingDog
class. The yodeling behavior went into a YodelingDog
class. However, we realized that both barking dogs and barkless dogs still shared some common behavior—all dogs pant. Thus, we kept the Dog
class and had the BarkingDog
and the YodelingDog
classes inherit from Dog
. Now Basenji
can inherit from YodelingDog
, and LhasaApso
and GoldenRetriever
can inherit from BarkingDog
.
We could have decided not to create two distinct classes for BarkingDog
and YodelingDog
. In this case, we could implement all barking and yodeling as part of each individual breed’s class—since each dog would sound differently. This is just one example of some of the design decisions that have to be made. Perhaps the best solution is to implement the barking and yodeling as interfaces, which we discuss in Chapter 8, “Frameworks and Reuse: Designing with Interfaces and Abstract Classes.”
In theory, factoring out as much commonality as possible is great. However, as in all design issues, sometimes it really is too much of a good thing. Although factoring out as much commonality as possible might represent real life as closely as possible, it might not represent your model as closely as possible. The more you factor out, the more complex your system gets. So you have a conundrum: Do you want to live with a more accurate model or a system with less complexity? You have to make this choice based on your situation, for there are no hard guidelines to make the decision.
For example, breaking up the Dog
class into BarkingDog
and the YodelingDog
models real life better than assuming that all dogs bark, but it does add a bit of complexity.
There will be instances in your design when the advantage of a more accurate model does not warrant the additional complexity. Let’s assume that you are a dog breeder and that you contract out for a system that tracks all your dogs. The system model that includes barking dogs and yodeling dogs works fine. However, suppose that you do not breed any yodeling dogs—never have and never will. Perhaps you do not need to include the complexity of differentiating between yodeling dogs and barking dogs. This will make your system less complex, and it will provide the functionality that you need.
Deciding whether to design for less complexity or more functionality is a balancing act. The primary goal is always to build a system that is flexible without adding so much complexity that the system collapses under its own weight.
Current and future costs are also a major factor in these decisions. Although it might seem appropriate to make a system more complete and flexible, this added functionality might barely add any benefit—the return on investment might not be there. For example, would you extend the design of your Dog
system to include other canines, such as hyenas and foxes (see Figure 7.5)?
Although this design might be prudent if you were a zookeeper, the extension of the Canine
class is probably not necessary if you are breeding and selling domesticated dogs.
So as you can see, there are always trade-offs when creating a design.
3.146.176.91