next up previous contents index
Next: 13.5 Polymorphism Up: 13. Object Oriented Computing Previous: 13.3 Short Introduction to

Subsections



13.4 Inheritance

Inheritance is a very powerful tool used in OOP to derive new classes from existing ones. First, look at an example.

Example 5   Investigating our Monte Carlo simulation more deeply, we find, that various types of elementary particles can be involved: photons, neutrons, neutrinos, etc.

On the one hand, we may conclude that one common data type, the Particle class, is sufficient for the representation of all the different particles, because they have many common features:

On the other hand, the way of the interaction with the detector is substantially different for different types of the particles. In some cases, it is described by mathematical formulae, in other cases it is described by measured data only. It follows that the operation representing the interaction of the particle in the detector must be implemented in a different way for different types of the particles, and this leads to the conclusion that different types of simulated particles have to be represented by different object types in the program; but these types share many common properties.

This situation - closely related, but different classes - can be expressed in the program model: OOP offers the mechanism of inheritance, which is the way of deriving one class from some other one (or other ones).

The class a new type is derived from is usually called the base class.

13.4.1 Base Class and Derived Class

The derived class inherits all the public and protected members of its base class or classes. This means that the derived class contains these members and may access them without any constrains. Private members are not inherited. They are not directly accessible in the derived class; they may be accessed only by the access methods inherited from the base class.

The derived class may add its own data members and methods to the inherited ones. The derived class may also redefine (override) some of the methods defined in the base class. (To override a method in a derived class means to implement a different response to the same message in the derived class.) In this case, the signature, i.e., the identifier, the return type, the number, and the types of the parameters of the overriding method in the derived class should be the same as the signature of the overridden method in the base class.

No members of the base class may be deleted in the inheritance process.

The set of all the classes connected by inheritance is usually called the class hierarchy.

Note that in some programming languages there are exceptions to the above rule. For example, the constructors, destructors, and overloaded assignment operators are not inherited in C++. Instead, the constructor of the derived class always calls the base class constructor and the destructor of the derived class always calls the base class destructor. The same holds for the default assignment operator of the derived class. Of course, this may be considered as a generalized form of inheritance.

13.4.2 Generalization and Specialization

The base class always represents a concept that is more general and more abstract, than the concept represented by the derived class; it follows that the derived class represents a more specialized concept than the base class. In other words, the derived class always represents a subclass - or a subtype - of its base class. Any instance of the derived class is also considered to be an instance of the base class.

The interface of the base class is a subset of the interface of the derived class.

Consequently, an instance of the derived class may be used everywhere where an instance of the base class is expected. This rule may significantly simplify the operation with instances of many similar classes.

In the UML class diagram, the inheritance is represented by an arrow ending with triangle (not filled). The arrow leads from the derived class to the base class.

Example 6   Consider the Particle class in our Monte Carlo simulation. This is a general concept that can be used to describe common features of all the particles involved. But in the simulation, concrete types of particles - e.g., protons, electrons, etc. - will be used.

Consequently, we will use the Particle class as the base class of the particles hierarchy that will contain the common data members and the common methods of all the particles. All the classes representing concrete particle types will be derived from the Particle class - see Fig. 13.3.

We will show here only the declaration of the Electron class.

class Electron: public Particle
{
public:
   Electron();
   void SetCharge(double _charge) { charge = _charge; }
   virtual void Interact(Detector *aDetector);
private:
   double charge;
};

Figure 13.3: The Particle class as a base class of concrete particle types
\includegraphics[width=10cm]{text/2-13/Vir03.eps}

The declaration of the Electron class contains only the declaration of the constructor, two access methods and one data member. Nevertheless, the methods GetvelocityX(), SetVelocityX(), GetActual() etc., inherited from the base class, may be called for any instance of this class. This class changes - overrides - the implementation of the Interact() method.

On the other hand, data members mass, velocityX, actual, etc. are not directly accessible in the Electron class. These data members are in the base class declared as private and the derived class must manipulate them only using the public access methods. So the following fragment of the definition of the Electron::Interact() method is incorrect:

// Error: velocityX, velocityY and velocityZ
// are inaccessible in the Electron class.
void Electron::Interact(Detector *aDetector)
{
   double velocity = sqrt(velocityX*velocityX +
            velocityY*velocityY + velocityZ*velocityZ);
   // ... and so on ...
}
The correct form uses the access methods inherited from the base class:

// Correct form of the previous code fragment
void Electron::Interact(Detector *aDetector)
{
   double velocity = sqrt(GetVelocityX()*GetVelocityX()
             + GetVelocityY()*GetVelocityY()
             + GetVelocityZ()*GetVelocityZ());
   // ... and so on ...
}
Of course, this is sometimes inconvenient. If we change the access specifiers of these data members in the base class to protected,
// Particle class definition revised
class Particle
{
public:
   // Public members as before
protected:
   // Instance data members
   double mass;
   double velocityX, velocityY, velocityZ;
   // Class data members
   static int actual;
   static int total;
};
the problems with access to data members will not appear. On the other hand, this violates the encapsulation of the base class and it may have a negative impact on the clarity and maintainability of the program.

13.4.3 Using Base Class as Common Interface

As stated above, instances of derived classes may be used anywhere instances of the base class are expected. This gives us very powerful tool to deal with the objects of the classes derived from the same base class in a uniform manner.

Example 7   In the Monte Carlo simulation of the particle experiment, we may first store all the emitted particles in a suitable container, exclude particles, that do not hit the detector etc., and after that preprocessing, let the remaining particles interact with the detector. Consider the following fragment of code:

const int N = 1{,}000{,}000;
        // Number of particles to emit
vector<Particle*> store;
        // Store for particles
Source sce1;
        // Particle source
Detector det;
        // Detector in the experiment
for(int i = 0; i < N; i++)
    store.push_back(sce1.Generate());
// ... some preprocessing of the set of the particles
for(int i = 0; i < store.size(); i++)
    store[i] -> Interact(det);

The store variable is a vector (dynamically sized array) of pointers to Particle, the base class of all the elementary particles involved. This allows us to store pointers to instances of any class derived from the Particle class in this container.

The expression

store[i] -> Interact(det);
represents the call of the Interact() method of the particle pointed to by store[i]. (In fact, this statement calls the method of the Particle class. To ensure that the method of the actual class of the particle is called, the method needs to be declared with the virtual specifier. This will be discussed later in Sect. 13.5, Polymorphism.)

This example demonstrates that the base class defines the common interface for all the derived classes.

13.4.3.1 Technical Note

The conversion from the derived class to the base class is automatic, but in some situations deserve special attention. Consider the following assignment:


Electron e;               // Non-dynamical instances
Particle p;
p = e;

After this statement has been executed, the p variable will contain an instance of the Particle class, not an instance of the Electron class! The reason is simple: The declaration

Particle p;
reserves store only for the Particle instance, so there is no place for the additional data members declared in the Electron class. The only way how to execute the assignment is to convert the derived class instance e to the base class first.

The same problem arises in the case of passing function arguments by value. Having the function

void process(Particle p);       // Pass by value
it is possible to write
process(e);                     // e is Electron
but the instance e of type Electron will be first cast (converted) to the Particle type. This cast leads to the loss of information.

These problems may be avoided if we use dynamical instances only. If we rewrite the preceding declarations into the form

Electron *ep = new Electron;   // Dynamical instances
Particle *pp;
pp = ep;                       // OK
the pp variable will still contain the pointer to the instance of the Electron class. (The type of the pointer is converted, not the type of the instance.)

The parameter of the process() function should be passed by the pointer or by the reference. In both cases, the original instance is accessible in the function body and no information is lost.

In Java, C$ ^{\char93 }$ and other OOP languages, that use dynamical instances of the object types only and manipulate them by references, these problems do not appear.


13.4.4 Inheritance, or Composition?

In some cases, it is not clear, whether a new class should be derived from some suitable class by inheritance or whether object composition should be used.

There are two questions that should be answered in this case:

This is known as the IS A - HAS A test. Only if the answer to the first question is yes, the inheritance may be considered, otherwise the composition should be used.

Example 8   Consider the Source class, representing the source of elementary particles in the Monte Carlo simulation. It will be based on some generator of random numbers represented in our program by the Generator class. In other words, the Source seems to be the Generator class with some added functionality. Should we derive the Source class from the Generator class?

If we apply the IS A - HAS A test, we find that the particle source is not a special case - a subclass - of the random number generator. It uses the random number generator, so it may contain it as a data member, however, the inheritance should be avoided in this case.

Consider for an instant that we use the inheritance to derive the Source class from the Generator class,

 // Wrong use of the inheritance
 class Source: public Generator
 {
    // Body of the class
 }
This would mean that we can use a Source instance everywhere the Generator instance is expected. But in the Monte Carlo simulation, the random number generator is necessary even in other parts of the program, e.g. for the determination of the interaction type, for the determination of the features of the secondary particles resulting from the interaction (if any) etc. However, in these parts of the program, the Source class may not serve as the random number generator.

Such a design of the Source class may cause that some typing errors in the program will not be properly detected and some mysterious error messages during the compilation will be reported; or even worse - it may lead to runtime errors hard to discover.

13.4.5 Multiple Inheritance

The class may have more than one base class; this is called multiple inheritance. The class derived from multiple base classes is considered to be the subclass if all its base classes.

Multiple inheritance may be used as a means of the class composition.

This feature is supported only in a few programming languages - e.g., in C++ (see [7]) or in Eiffel (see [6]). In Java, C$ ^{\char93 }$, Object Pascal and some other languages it is not supported. Multiple inheritance poses special problems, that will not be discussed here.


next up previous contents index
Next: 13.5 Polymorphism Up: 13. Object Oriented Computing Previous: 13.3 Short Introduction to