4.3 Encapsulation, inheritance, and polymorphism

The technique of putting data and methods inside a single object is called encapsulation. The two other words most commonly used when talking about OO are inheritance and polymorphism.

The idea behind inheritance is that if you already have a class that's almost like something you need, its a good idea to define a new class that is a child class of the existing class, recoding or overriding some of the new class methods so that they behave differently from the old class. We can draw a picture of inheritance as in Figure 4.4.

Figure 4.4. A class and a subclass


When an inherited method calls the code of the base method and then does something additional, we can say that it extends the method as well as saying that it overrides it. Thus, if ClassB inherits a foo() method from ClassB, we say that ClassB overrides foo so long as ClassB redefines the implementation code for foo in any way at all. And we can say that ClassB overrides and extends foo if the redefined code for foo has a form like ClassB::foo(){ClassA::foo(); doMore();}.

The MFC framework provides you with some key base classes called CWinApp, CDocument, and CView. Rather than reinvent the principles of Windows programming, we code our Pop program as a Windows application by implementing a CPopApp, CPopDoc and CPopView which are children of the standard MFC base classes. As we discussed in the last chapter, we draw pictures of inheritance relationships by drawing a hollow-headed arrow from the child to the parent class (see Figure 4.5). These kinds of drawings are the UML class diagrams that we mentioned before.

Figure 4.5. Inheritance diagram for basic MFC classes


The notion of polymorphism is that an object 'knows' what class it belongs to, and when you have it call some method, it will be sure to use the version of the method that's coded up by its class. This takes on special significance when you have a collection of objects belonging to disparate classes.

As a concrete example of polymorphism, let's think about having some classes that inherit from a class called cCritter. The cCritter class has an update() method that changes a critter object's state according to the current situation of the game world. Now it might be that we have several different kinds of critters in our program. This is illustrated in Figure 4.6. (As before, to make the UML class diagram cleaner, we use horizontal bars to combine into one arrow what could otherwise be drawn as separate inheritance arrows.)

Figure 4.6. Class diagram for cCritter child classes


Now suppose we were to have an array called biota which is an array of N pointers to cCritter objects. The prototype might be something like cCritter* biota[N]. And then we'd be able to update all the critters at once with a line like for(int i=0; i< N; i++) biota[i]->update(). And each biota[i] cCritter pointer object would know exactly which kind of cCritter child it was pointing to, and would know to use the appropriate version of the cCritter move method.

One annoying C++ gotcha is that in C++, a variable that can have child class values assigned to it will only show polymorphic behavior if it is a pointer variable.

That is, if the biota in the example just given were to be defined as cCritter biota[N] and the loop were to call biota[i].update(), then we would unhappily find that even if the various biota[i] objects were supposed to be differing kinds of cCritterChild classes, the base class cCritter::update would be executed for each of the biota[i] objects, with the actual child class information about these objects being totally ignored. The cause of this problem is that, in order to put a cCritterChild object childcrit into one of the biota[i] array slots, you'd actually need to 'upcast' it into a base cCritter object (cCritter)childcrit, thus losing its child class information. But a pointer variable works alright because a cCritterChild *pchildcrit pointer can be placed into a cCritter * pointer variable without having to change anything about the pointer.

This issue doesn't come up in Java, as all class object variables in Java are automatically pointers anyway. The moral is to use pointer-objects whenever you're planning to have them behave polymporphically. More information about this can be found in the reference Chapter 22: Topics in C++.

The two languages most used for OO these days are C++ and Java. This is not to say that there aren't others, such as Smalltalk and Ada 95. And Microsoft is currently promoting a new OO language called C# (pronounced 'C sharp'). Certainly most new applications are written with object-oriented code. This said, there are certainly a number of legacy applications that are in plain old C; this is particularly true for low-level programs such as device drivers.

Regarding Java and C++ for OOP, both have their pros and cons. At this moment in the history of computer science, a software engineer would do well to know both languages. C++ is a language of choice for stand-alone OO programs on a desktop machines, and Java is popular for distributed Web applications. By learning both languages you allow yourself a wider range of platform options. A less obvious point is that many aspects of OO only become really clear when you've learned more than one OO language. Learning Java has certainly increased this author's understanding of C++. And if you happen to know Java but not C++, learning C++ will undoubtedly increase your understanding of Java.

For a review of C++ and its OO features take a look at Chapter 22: Topics in C++ now. You may not want to read every detail of the chapter at this time, but at least skim through it, so that you'll know what information is there, and then you'll know where to look when you need it.

A little more terminology. The public methods for a class are sometimes called the class's interface. We often like to think of an object as a black box whose internals are hidden from the other objects. The interface to a black box like this is the methods you can use to make it do things.

A class normally has several different types of methods besides the constructors and the destructor. Specifically, accessors return information about an object's internal members, and mutators make changes to an object's members.

Occasionally we want to have a base class which doesn't actually have implementations of its methods. We can do this by giving the methods empty in-line code definitions, as in void doSomething(){}; or we can explicitly indicate that this method is not implemented at all with a line like void doSomething() = 0;. A method of the second type is called abstract, and a class with an abstract method is called abstract as well.

A base class with no data members and trivially defined or abstract methods is often called an interface. In the Java language there actually is an interface language construct that you can use in place of class to specify a base class with abstract methods.

Thus we can use the word interface in two senses. (a) If ClassB inherits from ClassA, then ClassB will have an interface (set of methods) that extends the interface (set of methods) of ClassA. (b) If ClassA really has nothing more than its set of methods, then we can simply speak of ClassA itself as being an interface.

    Part I: Software Engineering and Computer Games
    Part II: Software Engineering and Computer Games Reference