Inheritance

 <  Day Day Up  >  

Inheritance was defined in Chapter 1 as a system in which children inherit attributes and behavior from a parent class. However, there is more to inheritance, and in this chapter we will explore inheritance in greater detail.

Chapter 1 states that you can determine an inheritance relationship by following a simple rule: If Class B is a Class A, then this is a good candidate for inheritance.

Is-a

One of the primary rules of OO design is that public inheritance is represented by an is-a relationship. Let's revisit the mammal example used in Chapter 1. To present a very simple example, let's concentrate on 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).

Figure 7.1. A class diagram for the Dog class.

graphics/07fig01.gif


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, definition: 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).

Figure 7.2. The GoldenRetriever class inherits from the Dog class.

graphics/07fig02.gif

The GoldenRetriever class now contains its own behavior 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 the wheel by writing the bark and pant methods over again. Not only does this save some 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 tested).

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 are bred for retrieving, Lhasa Apsos are 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).

Figure 7.3. The LhasaApso class inherits from the Dog class.

graphics/07fig03.gif

Testing New Code

In our example with the GoldenRetriever class, the bark and pant methods should be written, tested, and debugged when the Dog class is written. Theoretically, this code is now robust and ready to reuse in other situations. However, the fact that you do not need to rewrite the code does not mean it should not be tested. Although it is unlikely , there might be some characteristic of a retriever that somehow breaks the code. The bottom line is that you should always test new code. Each new inheritance relationship creates a new context for using inherited methods. A complete testing strategy should take into account each of these contexts.


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? This inheritance model appears to work great. 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, but 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.

In our dog example, we have designed in the fact that all dogs can bark. However, there are dogs that do not bark. The Basenji breed is a barkless dog. These dogs do not bark, but 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.

Figure 7.4. The Dog class hierarchy.

graphics/07fig04.gif

Generalization and Specialization

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 still 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 .

Design Decisions

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.

What Computers Are Not Good At

Obviously a computer model can only approximate real-world situations. Computers are good at number crunching , but are not as good at more abstract operations.


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.

Model Complexity

At this level of our example, adding two more classes does not make things so complex that it makes the model untenable. However, in larger systems, when these kinds of decisions are made over and over, the complexity quickly adds up. In larger systems, keeping things as simple as possible is usually the best practice.


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 simply 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 really 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. In short, the return on investment is just not 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)?

Figure 7.5. An expanded canine model.

graphics/07fig05.gif

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 tradeoffs when creating a design.

Making Design Decisions with the Future in Mind

You might at this point say, "Never say never." Although you might not breed yodeling dogs now, sometime in the future you might want to do so. If you do not design for the possibility of yodeling dogs now, it will be much more expensive to change the system later to include them. This is yet another design decision that you have to make. You could possibly override the bark() method to make it yodel; however, this is not intuitive, as some people will expect a method called bark() to actually bark.


 <  Day Day Up  >  


Object-Oriented Thought Process
Object-Oriented Thought Process, The (3rd Edition)
ISBN: 0672330164
EAN: 2147483647
Year: 2003
Pages: 164
Authors: Matt Weisfeld

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net