© James E. McDonough 2017

James E. McDonough, Object-Oriented Design with ABAP, 10.1007/978-1-4842-2838-8_5

5. Inheritance

James E. McDonough

(1)Pennington, New Jersey, USA

The next stop on our journey to Objectropolis takes us from Abstraction to a place called Inheritance.

Let’s return to Table 4-3 (from the previous chapter) to describe the relationship between the abstraction levels for Enclosed shape, Enclosed shape with at least one angle, and Rectangle, shown again here in Table 5-1.

Table 5-1. Summary of the Members of the Three Classes

Classes

Enclosed shape

Enclosed shape with at least one angle

Rectangle

Attributes

area

perimeter

area

perimeter

smallest angle

area

perimeter

smallest angle

hypotenuse

Behaviors

getArea

getPerimeter

getArea

getPerimeter

getSmallestAngle

getArea

getPerimeter

getSmallestAngle

getHypotenuse

All three classes have a behavior for getArea. This means that each of these classes has its own implementation for a getArea method.

Upon further consideration, we might realize that not only does Rectangle represent a specialization of Enclosed shape with at least one angle, but in fact Rectangle is a Enclosed shape with at least one angle, and, likewise, Enclosed shape with at least one angle is an Enclosed shape. Indeed, we might conclude that the implementation we provided for the getArea method of Enclosed shape would work just as well for both Rectangle and Enclosed shape with at least one angle since Rectangle is a Enclosed shape and Enclosed shape with at least one angle also is a Enclosed shape.

Accordingly, instead of writing a unique implementation for the getArea methods of Rectangle and Enclosed shape with at least one angle, we can simply copy the implementation we wrote for the getArea method of Enclosed shape over to the getArea methods of these other classes. In fact, anytime we have this kind of “is a” relationship between two classes, we should be able to copy the implementation of the method from the more general class to the more specific class. Furthermore, this also applies to attributes, so we could copy our area and perimeter attribute definitions for Enclosed shape and paste them unchanged as the area and perimeter attribute definitions for both of the other classes.

Wow, just look how much work we are able to save ourselves! Rather than writing code from scratch, we can simply copy code from the more general class definitions into those classes with the more specific definitions wherever we find this “is a” relationship. It may be difficult for some of us to contain our euphoria upon realizing this reduction in our workload upon discovering Abstraction and that its aspect of levels of abstraction offers this “is a” concept.

Indeed, the designers of object-oriented principles also noticed this relationship and decided to implement a technique that eliminates even the need to copy the implementation code between classes related via “is a.” Instead of requiring programmers to replicate code, a language technique has been devised for programmers to simply indicate that an “is a” relationship exists between classes and let the language environment make the code defined in the more general class implicitly available to the more specific class. This capability, a fundamental principle of object-oriented programming, is known as inheritance.

Inheritance is what enables a class to have members that are not defined within the class itself. Instead, a class indicates those other classes from which it inherits members. This allows a class to reuse functionality defined in other classes and gives rise to the concept of a class hierarchy.1

Class Hierarchy

The ability for classes to inherit some of its members from another class begets a hierarchy of classes where each class indicates the other classes from which it inherits. So, let’s rework the collection of shape classes into an inheritance hierarchy, as shown Figure 5-1.

A447555_1_En_5_Fig1_HTML.jpg
Figure 5-1. Shape classes illustrated as an inheritance hierarchy

In this diagram, the classes still are arranged to reflect the vertical nature of their respective levels of abstraction, with each class providing less scope and more detail as we descend from the top of the diagram to the bottom, but now the connecting line between two classes denotes that the lower class inherits from the upper class.2 For this to occur, the lower class must indicate in its definition that it inherits from the upper class. We can now refer to Rectangle as a class inheriting from class Enclosed shape with at least one angle, and further can refer to class Enclosed shape with at least one angle as a class inheriting from class Enclosed shape.

The attributes area and perimeter, which previously had been defined for each of these classes, now can be defined only in the Enclosed shape class and their definitions inherited by both classes Enclosed shape with at least one angle and Rectangle. Similarly, the behaviors getArea and getPerimeter, which previously had also been defined for each of these classes, can now also be defined only in the Enclosed shape class and their definitions also inherited by both classes Enclosed shape with at least one angle and Rectangle. In this hierarchy diagram, class Enclosed shape with at least one angle inherits directly from class Enclosed shape by virtue of the vertical line connecting the two blocks representing their classes, but class Rectangle inherits indirectly from class Enclosed shape because the blocks representing these two classes are separated from each other by the intervening block representing class Enclosed shape with at least one angle.

Whereas class Rectangle previously required implementing four behaviors – one each for getArea, getPerimeter, getSmallestAngle, and getHypotenuse – now we can dispense with implementations for any of those behaviors it can inherit from other classes. In this case, class Rectangle need only provide an implementation for getHypotenuse since that is the only behavior that it cannot inherit directly or indirectly from another class. Through inheritance, class Rectangle can now make use of the implementation of behavior getSmallestAngle provided by class Enclosed shape with at least one angle, and from the implementations of behaviors getArea and getPerimeter provided by class Enclosed shape.

Various word pairs are used to describe the relationship between a class that offers inheritance to other classes and a class that takes advantage of this offer of inheritance. Table 5-2 shows pairs of words where the upper word/phrase of a pair describes the class offering inheritance and the lower word/phrase describes the class that does the inheriting.

Table 5-2. Combinations of Terms Used to Denote Inheritance

base class

ancestor class

parent class

superclass

derived class

descendant class

child class

subclass

The pair of terms chosen to be used in design discussions is usually the one primarily associated with the particular object-oriented programming environment. Much of the literature describing C++ uses the terms base class and derived class. With ABAP, the preference seems to be for the pair of words superclass and subclass, perhaps owing to the fact that the Object Builder transaction used for defining global classes has a property tab with a slot to designate the name of the class from which this class inherits, and its title is Superclass.

Some object-oriented languages have a root base class, provided by the language itself, existing at the top of the class hierarchy from which all other classes implicitly inherit. Languages C#, Java, and ABAP all have a root base class called “object.” The language Objective-C has a root base class called “NSObject.” In other object-oriented languages, such as C++, there is no concept of a root base class.

Each child class is aware of its parent classes because it must indicate in its definition the names of those classes from which it inherits. Meanwhile, a parent class knows nothing about any child classes it may have. This is an important distinction because a parent class should never contain any definitions or implementations for its behaviors which presume the existence of a specific child class .

Paths of Inheritance

The relationships between classes in an inheritance hierarchy establish a path of the possible ways descendant classes can inherit from ancestor classes. There is essentially no limit to the number of levels an inheritance hierarchy can have. Each class at the bottom of an inheritance path acquires all the attributes and behaviors of all the ancestor classes in all the paths leading to it. Figure 5-2 shows the inheritance hierarchy of the collection of shape classes side by side with a set of classes defining student employee.

A447555_1_En_5_Fig2_HTML.jpg
Figure 5-2. At left is an inheritance hierarchy of the collection of shape classes; at right is an inheritance hierarchy for the set of classes defining student employee

Once again, the hierarchy on the left shows that Rectangle “is a” Enclosed shape with at least one angle, which itself “is a” Enclosed shape, where the phrase “is a” denotes the clear inheritance path between the classes. The hierarchy on the right shows that StudentEmployee “is a” Student and also that StudentEmployee “is a” Employee.

Single Inheritance

In the case of class Rectangle, although it inherits from both Enclosed shape and Enclosed shape with at least one angle, its inheritance hierarchy is an example of single inheritance. Single inheritance is where each class has one and only one direct ancestor class. The relationship between class Rectangle and class Enclosed shape is indirect due to the intervening presence of class Enclosed shape with at least one angle in the inheritance path between them, but this still constitutes single inheritance due to the fact that all classes in the hierarchy have only one direct ancestor class.

Multiple Inheritance

In contrast, the inheritance hierarchy for class Student Employee is an example of multiple inheritance. Multiple inheritance occurs when a class has more than one direct ancestor class, as illustrated by class Student Employee having a direct ancestry path both to the Student class and to the Employee class.

Overriding Inherited Behaviors

As we have seen, a class inheriting from other classes will gain the functionality offered by the classes from which it inherits. Usually this is satisfactory, but occasionally we do not want to use the same implementation of a method offered by a parent class. Object-oriented languages enable us to ignore an inherited behavior and substitute a different implementation. This is known as overriding the behavior offered by the parent class. With method override, we indicate our intent to substitute the implemented behavior of an inherited method with a different behavior, but we are still bound to use the same signature for the method as it is defined in the parent class.

Method overriding is applicable only to instance methods. Whereas static methods can be inherited from a parent class, the class inheriting these methods cannot override their implementations.3

To illustrate an example of method overriding, suppose we have determined that the implementation for the getArea behavior implemented by the Enclosed shape class is very complicated due to the necessity to handle virtually any type of shape, and further that we have discovered that execution of this method takes its toll on computing resources as it calculates the answer. This very same implementation of getArea also is performed for class Rectangle since our class hierarchy indicates that Rectangle inherits indirectly from Enclosed shape.

Meanwhile, we know that the calculation for the area of a rectangle is simply the product of its height and width. Certainly this is much simpler than the complicated implementation for getArea we inherit from Enclosed shape. We can reduce the utilization of computing resources and improve the response time if we substitute this inherited implementation with a simple “height multiplied by width” calculation. To do this, we indicate that class Rectangle overrides the getArea method it inherits from Enclosed shape, and then we provide Rectangle with our simpler substitution implementation for calculating its area. Then, at execution time, when the getArea method of the Rectangle class is invoked, it will be the implementation of the getArea method specific to Rectangle that will provide the answer. We do not – indeed, cannot – change the signature of the getArea in any way; we simply change how to calculate the answer.

The preceding example is one where the behavior of a method inherited from a parent class is completely ignored in favor of a behavior provided by the child class. There also is the possibility that the child class not only overrides the inherited behavior but also intends to make use of it. In this scenario, the overriding implementation invokes the inherited implementation. This technique is often used to provide an implementation for a method at a more specific abstraction level with processing that includes but goes beyond the implementation provided by the parent class.

Let’s see this with an example. Suppose we define a new class Hollow Rectangle, which “is a” Rectangle with some inner shape area missing, as shown in Figure 5-3.

A447555_1_En_5_Fig3_HTML.jpg
Figure 5-3. Hollow rectangle

We indicate that Hollow Rectangle inherits from Rectangle. Its area is the area of the outer rectangle minus the area of the inner shape, which in this case is also depicted as a rectangle. Hollow Rectangle inherits the getArea method from Rectangle , but cannot allow that method to provide the area value since it does not account for the inner missing shape. Instead, Hollow Rectangle overrides getArea and provides its own implementation for the getArea method. However, one of the things this method still needs to do is calculate the area of the outer rectangle. Here is where an overriding method would be able to invoke the implementation of this method supplied by its parent class. In this case, the getArea method of Hollow Rectangle would first calculate the area of the inner shape, then invoke the getArea method implementation provided by parent class Rectangle to get the area of the outer rectangle, and then simply subtract the inner area value from the outer area value.

Object-oriented languages enable this capability by providing a way to explicitly invoke the method implementation defined in the parent class. In some languages, such as C++, the implementation in the child class invokes the same-named method in the parent class by qualifying the call to the method with the name of the parent class. Other languages provide a generic qualifier which serves as a reference to the parent class , such as “base” (C#) and “super” (both Java and ABAP).

Inheriting Unimplemented Behaviors

In some cases, a parent class will provide the name of a method and its signature but will not provide an implementation for the method. The intent here is that an inheriting child class will – in fact, must – provide the missing implementation. This is very similar to the case of overriding an inherited behavior, the difference here being that the parent class does not provide any implementation of the behavior to be overridden.

Indeed, methods defined in a parent class but lacking a corresponding implementation are known as abstract methods. Such methods must be marked abstract so the compiler will not require an accompanying implementation for them. Furthermore, a class that contains definitions for abstract methods must itself be defined as an abstract class. The significance of this designation at the class level is that abstract classes cannot be instantiated. The reason abstract classes cannot be instantiated is that they provide no implementation for any of their abstract methods .

Controlling Inheritance

In contrast to what most of us know about family genealogy, where parents choose to have children, with object-oriented inheritance hierarchies it is the child that gets to pick its parents. Accordingly, a child class indicates on its definition those classes from which it inherits. A class becomes a child class simply by naming one or more other classes as parents. This also implicitly designates the named classes as parent classes.

By marking it as a final method, a programmer can indicate that the method in a parent class may not be overridden by a substitute implementation in a child class. This means that any child classes must use the method as it is implemented in the parent class . This concept also applies to the class, which also can be marked as a final class. This means that the class cannot be used as a parent class; that is, no child classes are allowed from a class marked as final.4

Effects of Inheritance

The principle of inheritance casts a long shadow, causing effects upon some of the other aspects of object-oriented design we have covered already.

Effect of Inheritance upon Member Visibility

Inheritance offers classes the use of a new level of visibility that can be assigned to its members, which is more restrictive than public visibility but not as restrictive as private visibility. This level of visibility is known as protected. Members marked protected are visible only to the class in which the members are defined and to any child classes inheriting directly or indirectly from the class. In order for a child class to have visibility to a member it inherits from its parent class, the member must be defined at least with protected visibility. Those members defined in a parent class with private visibility are inaccessible to child classes.5

As mentioned, it is commonplace for attributes of a class to have a level of visibility more restrictive than the behaviors of the class. This concept is also applicable to inheritance. A class might provide public getter and setter methods to external entities that have no visibility to the attributes of the class, an arrangement embodied by the phrase “public behaviors and private attributes.” Private attributes of a parent class are not visible to a child class. It is protected visibility that makes inherited attributes visible to the child class at the same time keeping them invisible to external entities. This continues to conform with the idea of attributes having a level of visibility more restrictive than the behaviors of a class, but to accommodate the effect of inheritance we need to expand the visibility phrase to become “public behaviors and private and protected attributes.”

Effect of Inheritance upon Constructor Methods

As explained previously, instance constructor methods are invoked automatically during the instantiation of an object and static constructor methods are invoked automatically upon the first encounter of a statement referencing the class. With inheritance, the constructor methods of each parent class in the ancestry path also participate. A class in the inheritance hierarchy that does not explicitly define an instance constructor method or a static constructor method implicitly will have these made available to it.

Figure 5-4 shows an example inheritance hierarchy with a maximum depth of five levels and containing classes exhibiting both single and multiple inheritance . At the lowest level we see three classes of dogs (beagle, chihuahua, and dachshund) all inheriting from class dog. Class dog, in turn, inherits both from class canine and from class domesticPet, and is the only class in this inheritance hierarchy to inherit from more than one other class. We will use this diagram to describe how the static and instance constructors of parent classes get invoked.

A447555_1_En_5_Fig4_HTML.jpg
Figure 5-4. Inheritance hierarchy where class dog illustrates multiple inheritance

Effect of Inheritance upon Static Constructor Methods

As mentioned, a static constructor is used to initialize the static attributes of the class, does not have access to any of the instance members of its class and, because there is no specific statement that will cause a static constructor to be invoked, cannot include a method signature. Recall also that static constructors are invoked automatically by the runtime environment when a class is first accessed. Within a class inheritance hierarchy, the runtime environment insures that the static constructors of more specialized classes will be started only after all of the static constructors of more generalized classes in the hierarchy have finished.

To understand why it is necessary to delay static constructor execution until the static constructors of all superclasses have finished, let’s suppose that every one of the classes we see in the inheritance hierarchy diagram in Figure 5-4 includes static attributes that span the spectrum of visibility: public, protected, package, and private. For those static attributes with public and protected (and perhaps package) visibility, the static constructor of a subclass will also have access to these attributes during its processing. These attributes will need to be initialized by their own static constructor before the static constructor of the subclass can use them effectively. Accordingly, the static constructors of a class inheritance hierarchy are executed in order from most generalized class to most specialized class for whichever classes have not yet been accessed once already. So, when a class is accessed for the first time, the runtime system will identify the names of the classes participating in the inheritance hierarchy, make a list of them, sort the list from most generalized to most specialized, and then run through the list, starting with the most generalized class, executing the corresponding static constructor, if one exists and has not yet been executed.

For example, let’s say a program calls for the instantiation of a dachshund class, the class shown in the lower right of the inheritance hierarchy in Figure 5-4, and this is the first time any one of these classes has been accessed. The names of the classes participating in the inheritance hierarchy, from most general to most specific, are

  • animal

  • mammal

  • canine

  • domesticPet

  • dog

  • dachshund

The runtime environment will first execute the static constructor of the animal class. This is followed by executing the static constructor of the mammal class, which will now have available to it accurate initial values for the public and protected static attributes of the animal class. Then the static constructor of the canine class is executed, which will have available to it accurate initial values for the public and protected static attributes of both the animal and mammal classes. This process continues until finally the static constructor of the dachshund class is executed, which will have available to it accurate initial values for the public and protected static attributes of all the classes of its ancestry.

Notice that class domesticPet appears in the list between classes canine and dog, and represents a different branch in the inheritance structure from class dachshund. It is neither more general nor more specific than class canine, its predecessor entry in the list, since they do not share the same inheritance path. What is important here is that both the canine and the domesticPet classes precede the dog class in the list, because both are more generalized classes relative to class dog, and each of their respective static constructors must be executed prior to executing the static constructor of class dog.

For another example, let’s say a program first calls for the instantiation of an equine class, and this is the first time any one of these classes has been accessed. The names of the classes participating in the inheritance hierarchy in Figure 5-4, from most general to most specific, are

  • animal

  • mammal

  • equine

The runtime environment will execute the static constructors of the animal class, mammal class, and equine class, in that order. Subsequent processing in the same program calls for the instantiation of a fox class. Now the names of the classes participating in the inheritance hierarchy, from most general to most specific, are

  • animal

  • mammal

  • canine

  • fox

The runtime environment will execute the static constructors of the canine class and fox class, in that order. The static constructors for the animal and mammal classes will not be executed since they were already executed during the creation of the equine instance.

Effect of Inheritance upon Instance Constructor Methods

Remember that instance constructors are executed automatically by the runtime environment when a new object is being created, and because new instances of objects are requested explicitly, an instance constructor can include a method signature. Accordingly, the programmer causes an instance constructor to be executed by creating a new object.

In the previous section, it was explained how the runtime environment insures that, within a class inheritance hierarchy, the static constructors of more specialized classes will be started only after all of the static constructors of more generalized classes in the hierarchy have finished. In contrast, instance constructors of more specialized classes are started prior to the start of instance constructors of more generalized classes. In some object-oriented environments, the first action of the instance constructor must be to invoke the instance constructors of its superclasses. In other object-oriented environments, an instance constructor may perform some processing prior to invoking the instance constructors of its superclasses. C++ and Java fall into the former category where an instance constructor first must invoke its superclass instance constructors. ABAP falls into the latter category where an instance constructor may delay invoking the instance constructors of its superclasses. Regardless of which of these approaches is in effect, the same fundamental concept applying to static attributes also applies to instance attributes; specifically, a superclass needs to be able to initialize the public and protected instance attributes before a subclass can use them effectively. Indeed, an instance constructor must invoke the instance constructors of its superclasses at some point so that this initialization can occur.

For those environments in which an instance constructor may perform some processing prior to invoking the instance constructors of its superclasses, the programmer has control over the point at which the instance constructors of the superclasses get invoked. Until that point, the instance constructor may not make reference to any instance attributes or invoke any instance methods; its access is restricted to static attributes and static methods, parameters defined by the signature of the instance constructor, and any local variables that may have been defined within the instance constructor method. It is only upon return from invoking the superclass instance constructors when all instance attributes and methods become available to a constructor method.

Now, let’s return to the famous sentence we saw in Chapter 2:

  • The quick brown fox jumps over the lazy dog.

To review, we determined that this sentence implies two classes, a fox class and a dog class, and further, as shown in Table 5-3 (a copy of Table 2-1 from Chapter 2), that the dog class has an attribute for alacrity, and that the fox class has attributes for alacrity and color as well as a behavior for jump.

Table 5-3. The Beginning of an Object-Oriented Design

Class

Fox

dog

Attributes

alacrity

color

alacrity

Behaviors

jump

 

Table 5-4 includes the fox class and dog class, and all of their respective parent classes based on our example class inheritance hierarchy diagram shown in Figure 5-4. Here the attributes and behaviors shown for the fox and dog classes in the preceding chart have been assigned to the most appropriate inheritance level, where the attribute for color has been renamed to furColor. Some instance attributes and behaviors that might be associated with each class also have been included.

Table 5-4. Classes and Their Members for the Inheritance Hierarchy Described by Figure 5-4

Class

Attributes

Behaviors

animal

height

weight

age

alacrity

eat

sleep

speak

mammal

 

nurseOffspring

canine

furColor

run

jump

followScentTrail

domesticPet

petName

ownerName

lastVeterinarianVisit

getPetName

setPetName

visitVeterinarian

dog

 

chaseCat

fetch

fox

 

raidChickenCoop

Further, suppose that we have decided to define an instance constructor for class animal that accommodates setting a value for its alacrity attribute, and we have decided to also define an instance constructor for class canine that accommodates setting a value for its furColor attribute, as shown in Table 5-5.

Table 5-5. Indication of Classes Having Instance Constructors for Inheritance Hierarchy Described by Figure 5-4

Class

Attributes

Behaviors

Instance constructor

animal

height

weight

age

alacrity

eat

sleep

speak

yes

Mammal

 

nurseOffspring

no

Canine

furColor

run

jump

followScentTrail

yes

domesticPet

petName

ownerName

lastVeterinarianVisit

visitVeterinarian

no

Dog

 

chaseCat

fetch

no

Fox

 

raidChickenCoop

no

Table 5-6 shows the complete fox and dog classes with all of the attributes and behaviors inherited from other classes as illustrated in Table 5-5.

Table 5-6. Complete fox and dog Classes

Class

fox

dog

Attributes

height

weight

age

alacrity

furColor

height

weight

age

alacrity

furColor

petName

ownerName

lastVeterinarianVisit

Behaviors

eat

sleep

speak

nurseOffspring

run

jump

followScentTrail

raidChickenCoop

eat

sleep

speak

nurseOffspring

run

jump

followScentTrail

chaseCat

fetch

visitVeterinarian

In Chapter 2, we provided the following lines of pseudocode to fulfill the intent of the original statement:

create object fox
fox.set_color("brown")
fox.set_alacrity("quick")


create object dog
dog.set_alacrity("lazy")


fox.jump(dog)

This, of course, was before we understood about constructors. Now that we are familiar with the concept of constructors, we can use the following abbreviated pseudocode and get the same result:

create object fox("brown","quick")

create object dog(" "    ,"lazy")

fox.jump(dog)

Here we no longer have the statements explicitly setting the color and alacrity attributes after the instances of fox and dog have been created, but include this information with the request to instantiate these objects. This technique requires that we define constructor methods for both the fox and dog classes, which will set the instance attributes accordingly.

Consider that with this inheritance hierarchy, these attributes – alacrity and furColor (formerly simply called color) – are contributed by two different classes in the hierarchy: classes fox and dog both inherit the attribute furColor from class canine, but they inherit the attribute alacrity from class animal. Furthermore, recall that it was decided to define two instance constructors: one for class animal to facilitate setting a value for its alacrity attribute, and another for class canine to facilitate setting a value for its furColor attribute.

When an object is instantiated, it becomes an aggregation of its own attributes and methods and all of the attributes and methods it inherits from all classes in its inheritance hierarchy. In addition, all the instance constructors defined for each contributing class are executed to perform whatever actions are defined in those respective instance constructor methods. Those classes with no explicit instance constructor will have an implicit instance constructor that facilitates calling the instance constructor at the next highest level in the hierarchy , thus insuring that instance constructors defined throughout the inheritance hierarchy are executed.

In the case of the fox class, for the statement

create object fox("brown","quick")

the parameter values “brown” and “quick” are passed to the instance constructor methods accordingly. The instance constructor method for class canine would use “brown” to set the value of the furColor attribute it contributes to the new instance of fox, and the instance constructor method for class animal would use “quick” to set the value of the alacrity attribute it contributes.

Similarly, in the case of the dog class, for the statement

create object dog(" "    ,"lazy")

the parameter values “ “ and “quick” also are passed to the instance constructor methods. The instance constructor method for class canine would use a blank to set the value of the furColor attribute it contributes to the new instance of dog, and the instance constructor method for class animal would use “lazy” to set the value of the alacrity attribute it contributes.

We should recognize in this case that there are no instance constructor methods defined for classes fox, dog, or mammal, even though the instance constructors of their respective parent classes are executed.

This example vastly oversimplifies the interaction of instance constructors during instance creation. Each object-oriented environment has its own protocol for facilitating, during object instantiation, the call to the constructor method for each successive class in the inheritance hierarchy. There needs to be a way to pass up through the inheritance hierarchy only those values that apply to parent classes along the same line of ancestry, so that the respective instance constructor methods may perform their processing. To achieve this, each instance constructor invokes the instance constructor of its parent class.

Since the design calls for an instance constructor for class animal to set a value for attribute alacrity and an instance constructor for class canine to set a value for attribute furColor, let’s use pseudocode to define a sequence of instance constructor methods that can facilitate making these settings when we create an instance of class fox:

  • We indicated no instance constructor for class fox. In the absence of an instance constructor defined for a class, the runtime system will implicitly invoke the instance constructor for whatever next level in the class hierarchy provides one.

  • The next level in the class hierarchy for which we have indicated the presence of an instance constructor is class canine, which would be defined to accept parameter values for furColor and alacrity. It would use the parameter value for furColor to set the corresponding attribute it contributes, but would in turn invoke the instance constructor of its parent class passing to it the parameter value for alacrity:

    class canine constructor(furColor,alacrity)
      invoke superclass instance constructor(alacrity)
      set attribute furColor to parameter value furColor
  • The next level in the class hierarchy is mammal, for which we indicated no instance constructor. Again, in the absence of an instance constructor defined for a class, the runtime system will implicitly invoke the instance constructor for whatever next level in the class hierarchy provides one.

  • The next level in the class hierarchy for which we have indicated the presence of an instance constructor is class animal, which would be defined to accept a parameter value for alacrity, using it to set the corresponding attribute it contributes:

    class animal constructor(alacrity)
      set attribute alacrity to parameter value alacrity

At the conclusion of this instance constructor invocation sequence, the instance constructor defined for each class within the ancestry hierarchy has had its chance to perform its processing. Afterward, the instance created (in this case, fox) now is in a state where it can be used.

Traversing the inheritance hierarchy of class fox shows an example of a single inheritance ancestry. Class fox has only one parent class (canine) which itself has only one parent class (mammal) which also has only one parent class (animal).

Class dog also follows this same ancestry path as fox, but dog also inherits from class domesticPet. This is an example of multiple inheritance; class dog has multiple parent classes. Consequently, when a dog instance is created, in addition to invoking the same instance constructors invoked for class fox, there is also the necessity to invoke the instance constructors of any classes in the ancestry path through domesticPet. Each object-oriented language supporting multiple inheritance has its own protocol for the sequence of invoking the instance constructors of these multiple inheritance paths. For example, in C++, the instance constructors of the multiple parent classes defined for a class are invoked in the same sequence as they appear on the definition of the class, and changing this sequence will change the order in which the instance constructors are invoked.

Effect of Inheritance upon Reference Variables

Reference variables enable a component to hold a pointer to an instance of a class. When a reference variable is defined in a program, it is associated with the type of class of which it is capable of holding a reference.6 However, a fundamental concept in object-oriented programming is that a reference variable may hold not only a reference to the type of class with which it is associated but may hold a reference to any type of child class inheriting directly or indirectly from its associated class. In short, a reference variable can hold a pointer to any type of class instance as long as the instance “is a” type of class assigned to the reference.

Because of this, a reference variable has an additional quality to be considered beyond just the instance of a class to which it is pointing. This additional quality is known as the “reference variable type .” There are two aspects of a reference variable type:

  • Static type

  • Dynamic type

Every reference variable has both of these two aspects of reference variable type. In some object-oriented languages, this concept of dynamic type is known as runtime type information (RTTI).

The static type refers to the type of class assigned with the definition of the reference variable. It is known as static because the programmer assigns a type of class to the reference variable and it will not change once the source code is compiled. Changing a static type to some other type of class requires a change to the source code.

The dynamic type refers to the type of class to which the reference variable is actually pointing during execution. It is known as dynamic because the type of class to which the reference variable is pointing may change from moment to moment during program execution. Changes to the dynamic type are a consequence of moving a pointer to an object into the reference variable.

When the type of class instance to which a reference variable is pointing at execution is the same as the one with which the reference variable is associated, then the static type and dynamic type of the reference variable are identical. When the type of class instance to which a reference variable is pointing at execution is a direct or indirect child class to the one with which the reference variable is associated, then the static type and dynamic type of the reference variable are different. Without inheritance, the static type and dynamic type of a reference variable would always be the same, and perhaps it would be unnecessary even to be aware of the concept of reference variable type . However, it is when inheritance is introduced that a dynamic type of a reference variable could be different from its static type.

A fundamental concept in object-oriented programming is that a reference variable can offer access only to those members of an instance that are defined by the class associated with its static type. That is, the static type of a reference variable determines which instance members are accessible through the reference variable. Accordingly, we can define two reference variables to two different abstraction levels within the same class inheritance hierarchy, like

this_enclosed_shape      type enclosed_shape
this_rectangle           type rectangle

and then create an instance of a rectangle into the reference variable with the static type defined for class rectangle, like

create object this_rectangle

and then copy the pointer to this instantiated class to the other reference variable, like

this_enclosed_shape = this_rectangle

At this point we have two reference variables both pointing to the same instance. Whereas reference variable this_rectangle has both a static type and dynamic type of rectangle, reference variable this_enclosed_shape has a dynamic type of rectangle, but its static type is enclosed_shape. Despite that both are pointing to exactly the same instance of a rectangle, we can access the method get_hypotenuse only through the reference variable this_rectangle. Access to the members of the rectangle instance through reference variable this_enclosed_shape is limited to only those members made available by class enclosed_shape, since that is the static type of the reference variable.

Effect of Inheritance upon Subsequent Maintenance

When used intelligently, inheritance is an indispensable object-oriented principle used in the design of software. When used indiscriminately, inheritance can cause difficulties with software design and maintenance. Steve McConnell states this very succinctly:

  • “Inheritance is one of object-oriented programming’s most powerful tools. It can provide great benefits when used well, and it can do great damage when used naively.”7

Of the four pillars of object-oriented programming , inheritance is arguably the one that needs to be approached with the most caution. It is tempting to define elaborate class inheritance hierarchies once programmers become aware of its power. This can lead to class relationships that in subsequent maintenance cycles become too difficult to comprehend or require significant development effort to manage effectively.

Having been introduced with the first generation of object-oriented languages, inheritance has since fallen out of favor with many object-oriented scholars who now recommend using a technique known as composition. 8 This is not to suggest that inheritance should not be used, but simply that it requires some discipline on the part of software designers in deciding where it is applicable. The conventional wisdom these days is that inheritance hierarchies should remain shallow.9 I usually recommend to my students that three levels of inheritance are easily managed and that four levels still are acceptable, but once the hierarchy extends to five or more levels we are exposing ourselves and any subsequent maintenance programmers to potential difficulties managing and understanding the hierarchy.10

Meanwhile, inheritance enables writing entirely new classes that include members defined in other classes. This reuse of established components facilitates constructing new entities from those that already exist without the need to change the existing entities. Accordingly, a new class inheriting from an existing class can benefit not only from the code the parent class contributes but also from all the testing to which the parent class has already been subjected, minimizing the amount of work necessary to prepare a new subclass for use.

Effect of Inheritance upon Design

The extensive use of inheritance imposes certain constraints upon program design, including

Singleness

Inheritance is restricted to only a single superclass.

Static

Inheritance hierarchies are fixed during execution and cannot change other than by modifying code to rearrange the structure of the inheritance hierarchies.

Visibility

An entity having access to an object also has access to the members defined by its superclass.

Some object-oriented languages, among them Java and ABAP, have accommodated these design constraints as fundamental restrictions built into those languages. Neither language supports multiple inheritance , leaving singleness not so much a design choice as a design requirement. Both languages adhere to the static nature of inheritance hierarchies, but also provide support for using object composition as well. Both languages also facilitate subclass access to its superclass members by offering the protected access modifier for confining the visibility of some of its members only to its inheritors, avoiding the requirement for the superclass to otherwise make those members publicly visible.

Indeed, languages that lack support for multiple inheritance entirely eliminate the possibility of a classic design problem from ever creeping into software systems. Known as the diamond problem, 11 it occurs when a class inherits from two parent classes, where at least one of these classes overrides a method of the same class from which both inherit. It gets its name from the shape of the relationships between classes composing this type of hierarchy, as illustrated in Figure 5-5.

A447555_1_En_5_Fig5_HTML.jpg
Figure 5-5. Illustration of the diamond problem

Here we see through the connecting lines that class Student Employee inherits both from class Student and from class Employee, and further that classes Student and Employee both inherit from class Person, and that this constitutes a diamond relationship as depicted by the gray background shape.

Suppose class Person provides both the definition and implementation for a public method called getInformation, and that class Employee overrides the implementation of this method. It now becomes indeterminate which implementation will be applicable to class Student Employee, since the original implementation provided by Person is available via its Student superclass but the overridden implementation is available via its Employee superclass. Accordingly, since they both enforce the singleness design constraint, neither Java nor ABAP succumbs to the infamous diamond problem.

Manipulating Values in Reference Variables

Since it is inheritance that introduces the possibility where the static type and dynamic type of a reference variable can be dissimilar, it is only fitting that we devote some attention to the exchange of values between object reference variables when inheritance is in effect.

Moving a value between reference variables where both source and target are defined the same way (identical static types) is a simple move where only the pointer value is copied from one to the other. After such a move, there will be (at least) two reference variables pointing to the same instance of a class.

By contrast, moving a value between reference variables where the source and target are not defined the same way (dissimilar static types) is valid only when the dynamic type of the source is compatible with the static type of the target. By compatible, we mean that the dynamic type of the source “is a” static type of the target. Such moves between object reference variables with dissimilar static types fall into one of the following three categories:

  • It is never valid.

  • It may be valid.

  • It is always valid.

The class hierarchy shown in Figure 5-6 will be used to illustrate the circumstances under which such reference variable exchanges are never valid, may be valid, and are always valid.

A447555_1_En_5_Fig6_HTML.jpg
Figure 5-6. Hierarchy used to illustrate the validity of reference variable exchange

This class inheritance hierarchy shows a vehicle class with two child classes: truck and car. The truck class also has two child classes (tractor-trailer and straight-truck), while the car class has three child classes (SUV, sedan, and convertible).

A move between reference variables is never valid when the types of classes represented by the source and target reference variables do not lie in the same child-to-parent path in the class inheritance hierarchy. An example of this is when we have a reference variable to class car and we attempt to move it to a reference variable to class truck. Truck and car are not in the same child-to-parent path in the inheritance hierarchy. That is, we cannot start in the hierarchy at either of these classes and find the other one by moving only from a child class to its parent class. As such, a reference variable defined as type car (its static type ) can never have a dynamic type that is compatible with a reference variable defined as type truck. Similar never-valid moves apply from SUV to sedan, from convertible to straight-truck, and from tractor-trailer to car for the same reason that we can never move between the two classes in the hierarchy using only a child-to-parent movement.

A move between reference variables is always valid when both of the following conditions are true:

  • The types of classes represented by the source and target reference variables do lie in the same child-to-parent path in the class inheritance hierarchy.

  • The target is the direct or indirect parent class of the source.

An example of this is when we have a reference variable to class car and we attempt to move this to a reference variable to class vehicle. Since vehicle is the direct parent class to car, the move is always valid. As such, a reference variable defined as type car (its static type ) will always have a dynamic type that is compatible with a reference variable defined as type vehicle. Similar always-valid moves apply from sedan to car, from straight-truck to truck, and from convertible to vehicle for the same reason that we always can move from a source to a direct or indirect target using only a child-to-parent movement. A useful metaphor for keeping this straight is to understand that the move is always valid when the source “is a” target.

A move between reference variables may be valid when both of the following conditions are true:

  • The types of classes represented by the source and target reference variables do lie in the same child-to-parent path in the class inheritance hierarchy.

  • The source is the direct or indirect parent class of the target.

An example of this is when we have a reference variable to class car and we attempt to move this to a reference variable to class convertible. Since car is the direct parent class to convertible, the move may be valid, but it is valid only when the reference variable to class car holds a reference to an instance of class convertible. That is, the move is valid only when the dynamic type of the source is compatible with the static type of the target. Since the dynamic type of the source object reference variable cannot be known until execution, such moves require identifying the dynamic type of the source during execution to determine its compatibility with the target, and is the reason why the move may be valid.

Casting

When a move occurs between two reference variables with dissimilar static types, the corresponding instance to which the source variable is pointing is made available as though it were an instance of the static type of the target variable. This is known as casting the instance. No change takes place to the instance itself; what does change is the level of abstraction to the instance accessible through the target variable. This level of abstraction becomes either more general or more specific depending on whether the static type of the target reference variable is, directly or indirectly, a child class or parent class to the static type of the source reference variable.

This idea of casting is usually associated with a qualifier further describing the type of casting. Some programmers refer to casting by the terms up casting and down casting, which refer to the direction in the class inheritance hierarchy one traverses when moving between inheritance levels. Others refer to this as widen casting and narrow casting, which refers to a range or precision aspect associated with the different inheritance levels. Unfortunately, many explanations for the definition of these terms over the years by various object-oriented scholars have become so contradictory as to render these terms meaningless. Let’s see why.

In the case of up and down casting, many have described the class inheritance hierarchy using the tree metaphor. With this metaphor, classes are described as having branches to child classes, and to move from any leaf of the tree in the direction where all branches converge is to move down the tree. Others have described the inheritance hierarchy as a corporate organization chart, with a president at the top and branches descending to vice presidents of marketing, manufacturing, sales, personnel, etc., with each of these having subsequent branches to other levels, eventually identifying everyone in the corporation. With this metaphor, classes also are described as having branches to child classes, but in this case moving in the direction where all branches converge is to move up the corporate ladder. Thus there are two opposing definitions for what is meant by up and down, generating confusion by those who use these terms.

Similarly, widen casting has been used by some to describe movement to a target class having more members than the source class, the target class offering a wider array of attributes and/or behaviors. The class with more members is the child class. Going the other direction constitutes narrow casting due to moving to a class with fewer members – to the parent class. Others use widen casting to describe movement to a target class accommodating a larger set of classes than that handled by the source class – to the parent class – and use narrow casting to describe movement to a target class accommodating only a subset of classes handled by the source – to the child class. Again, we have two opposing definitions for what is meant by widening and narrowing, continuing the confusion further.

I have chosen to abandon the terms up casting, down casting, widen casting, and narrow casting in favor of the following terms:

  • Generalizing cast

  • Specializing cast

One of the classes will be more specialized than the other, and it would be difficult to invert the meaning to the extent where a parent class could be considered more specialized than one of its child classes. Accordingly, moving an object pointer from a reference variable of class car to a reference variable of class vehicle constitutes a generalizing cast, since the receiving field defines a reference to a class more general than that of the source field. In contrast, moving an object pointer from a reference variable to class vehicle to a reference variable of class car constitutes a specializing cast, since the receiving field defines a reference to a class more specialized than that of the source field.

As noted in the previous section on manipulating values in reference variables, moving object pointer values between reference variables will work only when the dynamic type of the source variable is compatible with the static type of the target variable; that is, the source dynamic type “is a” target static type. Accordingly, a generalizing cast will always work because the dynamic type of the source variable always “is a” static type of the target variable. By contrast, a specializing cast may work and it may not work. When the source dynamic type “is a” target static type, then the casting move will work, but when the source dynamic type “is not a” target static type, then the casting move will fail. As such, we need to accommodate those attempts when a casting move assignment would not or does not work.

For specializing casts , each object-oriented language has its own ways of determining whether such a casting move assignment will work or did work. Java has the “instanceof” operator to test the dynamic type of the source variable against a known static type , so that it can avoid making the casting assignment when this test fails, and is an example of checking whether the casting assignment will work; that is, we check the assignment validity first before making the assignment. The languages C++ and ABAP provide what is known as a try-block, where the casting move assignment is embedded in a protected block of code that has catch clauses to intercept any exceptions thrown by the failure of the assignment. The applicable metaphor used here with try-blocks and catch clauses is a ball: when a casting move assignment fails, the try-block “throws” the ball (exception) and the catch clause “catches” the thrown ball (exception). Each catch clause identifies the type of exception(s) it is capable of intercepting. Try-blocks are not limited only to exception processing for casting move assignment failures; they are used to intercept other types of runtime failures, such as object creation failures, arithmetic failures, resource unavailable failures, etc. When used with casting, try-blocks and their catch clauses are examples of checking whether the casting move did work; that is, we try to make the assignment and then determine afterward whether it succeeded.

Metaphors for Understanding the Concept of Reference Variable Type

I have found that grasping the concept of reference variable type can be elusive to some programmers, due in part to describing its aspects using words like static and dynamic, words that have different meanings in other contexts of object-oriented programming. To solidify this concept, it may be helpful to use metaphors.

Postal Metaphor

The President of the United States lives in the White House. Mail addressed to the holder of this office requires something similar to the following:

  • 1600 Pennsylvania Avenue NW

  • Washington, D.C.

This would probably be enough for mail originating within the United States, but for parcels mailed from other countries, the following enhanced address might be required:

  • 1600 Pennsylvania Avenue NW

  • Washington, D.C.

  • USA

Someday in the distant future it may be necessary to extend this address with other qualifiers , such as

  • 1600 Pennsylvania Avenue NW

  • Washington, D.C.

  • USA

  • North America

  • Earth

  • Solar System

  • Gould Belt

  • Orion Arm

  • Milky Way

  • The Universe12

Absurd, perhaps, but this illustrates a series of locations where each subsequent entry is more general than the one it follows.

Recall the red brick patio analogy from the chapter on Abstraction, where the level of abstraction above the patio determined the detail we could see. Let’s apply this same concept to intergalactic mail destined for the White House. First, let’s sort the address qualifiers in order from most general to most specific:

  • The Universe

  • Milky Way

  • Orion Arm

  • Gould Belt

  • Solar System

  • Earth

  • North America

  • USA

  • Washington, D.C.

  • 1600 Pennsylvania Avenue NW

They are now arranged such that we can ascend from 1600 Pennsylvania Avenue NW to a higher abstraction level to see all of Washington, D.C., and from there still higher to see all of the United States, and so on, moving from a distance of 5 meters above a patio at 1600 Pennsylvania Avenue NW to 5 kilometers above, to 5 parsecs above, until finally measuring our distance above the patio in light years.

The parcel posts (mail service providers) handling postal traffic corresponding to these abstraction levels might have designations as shown in Table 5-7.

Table 5-7. Address Qualifier and Corresponding Parcel Post

Address qualifier

Parcel post

The Universe

Universe

Milky Way

Galaxy

Orion Arm

Galaxy sector

Gould Belt

Galaxy subsector

Solar System

Star system

Earth

Planet

North America

Continent

USA

Country

Washington, D.C.

City

1600 Pennsylvania Avenue NW

Neighborhood

In each case, the parcel post corresponds to a class capable of handling mail at that abstraction level . Each of these classes would have behaviors relevant to their abstraction levels, as indicated in Table 5-8.

Table 5-8. Address Qualifier, Corresponding Parcel Post, and Associated Behaviors

Address qualifier

Parcel post/Class

Behavior

The Universe

Universe

 

Milky Way

Galaxy

Route parcel to other galaxy post of this universe.

Route parcel to sector post of this galaxy.

Orion Arm

Galaxy sector

Route parcel to this galaxy post.

Route parcel to other sector post of this galaxy.

Route parcel to subsector post of this galaxy sector.

Gould Belt

Galaxy subsector

Route parcel to this galaxy sector post.

Route parcel to other subsector post of this galaxy sector.

Route parcel to star system post of this galaxy subsector.

Solar System

Star system

Route parcel to this galaxy subsector post.

Route parcel to other star system post of this galaxy subsector.

Route parcel to planet post of this star system.

Earth

Planet

Route parcel to this star system post.

Route parcel to other planet post of this star system.

Route parcel to continent post of this planet.

North America

Continent

Route parcel to this planet post.

Route parcel to other continent post of this planet.

Route parcel to country post of this continent.

USA

Country

Route parcel to this continent post.

Route parcel to other country post of this continent.

Route parcel to city post of this country.

Washington, D.C.

City

Route parcel to this country post.

Route parcel to other city post of this country.

Route parcel to neighborhood post of this city.

1600 Pennsylvania Avenue NW

Neighborhood

Route parcel to this city post.

Route parcel to street address of this neighborhood.

The behaviors associated with each parcel post/class represent the postal capabilities available at that corresponding parcel post abstraction level. Where applicable, the behaviors accommodate moving a parcel up to the superordinate parcel post, laterally to a sibling parcel post, or down to a subordinate parcel post.

If we were to define a parcel post object reference variable associated with the class planet, its static type would be planet and the capabilities it could offer as services would be limited to these three:

  • Route parcel to this star system post.

  • Route parcel to other planet post of this star system.

  • Route parcel to continent post of this planet.

Now, suppose a postal item intended for the President of the United States originated long ago in a galaxy far, far away. The intergalactic mail service facilitating postal traffic throughout The Universe would route this to the Milky Way parcel post. At this point the intragalactic mail service facilitating the Milky Way would take over and route this to the Orion Arm parcel post. It would pass through each successive, more-specific parcel post, including Earth, until it eventually reached the White House. The parcel post handling the northwest (NW) neighborhood of Washington, D.C. is at an abstraction level at which it has visibility to the detail of the roads in that neighborhood, including Pennsylvania Avenue. Meanwhile, roads (members of a neighbourhood) are at a level of detail beyond the perception of the parcel post handling Earth, which can recognize only its own star system, other planets in this star system, and continents on this planet. Similarly, continents (members of a planet) are at a level of detail beyond the perception of the parcel post handling Solar System, which can recognize only its own galaxy subsector, other star systems in this galaxy subsector, and stellar bodies orbiting the sun.

Although 1600 Pennsylvania Avenue exists as a detail in The Universe, it cannot be detected by the various parcel posts until we get to a level of abstraction close enough where this level of detail becomes visible.

Think of each parcel post as providing a virtual visor by which its mail carriers can see the detail of its own members. This visor offers visibility only to those members available through the static type of the parcel post. Despite handling a piece of mail with a more dynamic characteristic, it can provide visibility only at the level of detail the parcel post’s static type visor permits. That is, although the parcel may indeed have a street address in Washington, D.C., a mail carrier working at the Earth parcel post cannot perceive a city level of detail, and so has no behavior for routing the mail directly to any city.

For a different perspective, let’s suppose we have traveled the two and a half million light years from Earth to the Andromeda galaxy and have been able to establish an astronomical observatory on an earth-like planet, equipped with a telescope having the latest state-of-the-art technology available. Although we could aim the telescope toward the Milky Way galaxy, which we know has a Planet Earth with a President of the United States living in the White House at 1600 Pennsylvania Avenue NW, Washington, D.C., the magnifying power of our telescope would not enable us to perceive the Solar System, let alone Planet Earth, let alone Washington, D.C., even though we could be certain these would lie within our field of vision.

Cinematic Metaphor

Let’s suppose we are involved with filming a movie titled The Smith Family Goes To The County Fair. The script writers have called for different characters to be portrayed in the various scenes, and each character plays a specific role. Some of the required characters and their respective roles are shown in Table 5-9.

Table 5-9. Characters and Roles Associated with the Movie Being Filmed

Character

Role

Edward Smith

Father of the Smith family

Margaret Smith

Mother of the Smith family

Jessica Smith

Teen daughter of the Smith family

Frederick Smith

Preteen son of the Smith family

Karen

Contestant in women’s three-legged race

Susan

Contestant in women’s three-legged race

William

Contestant in men’s three-legged race

Brian

Contestant in men’s three-legged race

Pat

Owner of vehicle on display at classic car show

Chris

Driver of vehicle in demolition derby

Shelly

Winner of pie-eating contest

Extras

Spectators at classic car show

Extras

Spectators at demolition derby

Extras

County Fair attendees

To fill these roles, we need actors, persons with the ability to portray a role in a movie.

Now that we have identified character, role, and actor, we can draw the analogy between film-making and object-oriented programming:

  • A character in this movie is analogous to a reference variable in an object-oriented program.

  • A role in this movie is analogous to the static type of a reference variable in an object-oriented program.

  • An actor is analogous to the dynamic type of a reference variable in an object-oriented program.

This is illustrated in Table 5-10.

Table 5-10. Relationship Between Features in Object-Oriented Programming and Features in Film Making

Features in object-oriented programming

Analogous features in film-making

reference variable

character

reference variable static type

role

reference variable dynamic type

actor

The roles require both male and female actors as well as adult and youngster actors. Accordingly, gender and age become specific character traits required for some of the roles, and the roles have been listed in the order they require specific gender and/or age traits. The roles for the Smith family characters require both gender and age-specific traits. They are followed by roles for contestants in the women’s and men’s three-legged races, which require specific gender but not specific age. They are followed by roles for owners of cars in the classic car show and drivers participating in the demolition derby, which require adult age but not specific gender. They are followed by the role for the winner of the pie-eating contest as well as the roles for spectators and attendees, which require neither specific gender nor specific age, and are simply listed as Shelly (winner of the pie-eating contest) or Extras (unnamed characters filled by those who respond to a casting call to be extras in a movie).

Before we begin shooting, our casting director will assemble the names of actors and begin the task of assigning actors to roles. Accordingly, the casting director will identify which types of actors can be assigned to which roles based on the gender and age requirements of each role, as shown in Table 5-11.

Table 5-11. Characters, Roles, and Actors Associated with the Movie Being Filmed

Character

Role

Actor

Edward Smith

Father of the Smith family

Male adult

Margaret Smith

Mother of the Smith family

Female adult

Jessica Smith

Teen daughter of the Smith family

Female youngster

Frederick Smith

Preteen son of the Smith family

Male youngster

Karen

Contestant in women’s three-legged race

Female

Susan

Contestant in women’s three-legged race

Female

William

Contestant in men’s three-legged race

Male

Brian

Contestant in men’s three-legged race

Male

Pat

Owner of vehicle on display at classic car show

Adult

Chris

Driver of vehicle in demolition derby

Adult

Shelly

Winner of pie-eating contest

Any

Extras

Spectators at classic car show

Any

Extras

Spectators at demolition derby

Any

Extras

County Fair attendees

Any

The role (static type ) Father of the Smith family calls for a male adult. The actor (dynamic type ) assigned to play that role needs to be both male and adult. In this case, the requirement of the role (static type) and the traits of the assigned actor (dynamic type) are identical; both are male adult. This same concept applies to the role Mother of the Smith family (female adult), to teen daughter of the Smith family (female youngster), and to preteen son of the Smith family (male youngster), where the requirement for the role matches the traits of the assigned actor. For reference variables, this is similar to both its static type and dynamic type being identical.

The role of Susan calls for a female actor. It may be played by an adult or a youngster, so long as the actor is female. In this case, the actor will have more of a specialization than is required for the role (she will be adult or youngster), but certainly has the necessary trait called for by the role (female). As such, the role will call upon the gender of the actor but not upon the age of the actor. So, even though the role may be filled by a youngster actor, the role does not require the actor to act in any way specific to being a youngster. Accordingly, the actor has excess capacity that is not required for the role. For reference variables, this is similar to its static type being more general than its dynamic type.

Similarly, the role of Chris calls for an adult actor. It may be played by a male or female, so long as the actor is adult. Again in this case, the actor will have more of a specialization than is required for the role (actor will be male or female), but certainly has the necessary trait called for by the role (adult). As such, the role will call upon the age of the actor but not upon the gender of the actor. So, even though the role may be filled by a female actor, the role does not require the actor to act in any way specific to being female. Here again the actor has excess capacity that is not required for the role, and again is similar to a reference variable static type being more general than its dynamic type .

Likewise, the role of Shelly calls for any actor. It may be played by a male or female , adult or youngster. Again in this case, the actor will have more of a specialization than is required for the role (he or she will be adult or youngster), but certainly has the necessary trait called for by the role (any). As such, the role will call upon neither the age nor the gender of the actor. So, even though the role may be filled by a male youngster actor, the role does not require the actor to act in any way specific to being male or being a youngster. Here again the actor has excess capacity that is not required for the role, and again is similar to a reference variable static type being more general than its dynamic type .

In all of these cases, the role makes no assumption about the actor playing the part, and does not call upon the actor to behave in any way beyond the requirements of the role. Accordingly, only those behaviors offered by the role can be portrayed by the actor. For reference variables, this means that only those behaviors offered by its static type can be requested by the corresponding acting object occupying the reference variable, despite the acting object having behaviors beyond those associated with the static type.

Our casting director will also want to assign understudies to some of these characters.

In those cases where an actor is assigned to a role that requires more specialization than a role for which the actor is being considered for understudy, the casting director does not need to be the least bit cautious of possibly assigning an actor to a role they cannot fill; the understudy assignment is always applicable to a role requiring less behavior than the role already assigned. For example, the actor playing the role of Frederick Smith (preteen son of the Smith family) is assigned to a role in which gender and age already are requirements. Accordingly, this actor can be assigned as an understudy for any of the other roles that require only male (characters William and Brian) or that require neither gender nor age (character Shelly). Accordingly, the casting director could cast the actor playing the Frederick Smith character as the understudy to the William, Brian, or Shelly characters without having to consider whether such a casting is incompatible. This is analogous to a generalizing cast between reference variables; it is always valid.

In those cases where an actor is assigned to a role that requires less specialization than a role for which the actor is being considered for understudy, the casting director will need to exercise caution over possibly assigning an actor to a role they cannot fill; the understudy assignment may not be applicable to a role requiring more behavior than the role already assigned. For example, the actor playing the role of Shelly (winner of pie-eating contest) is assigned to a role in which gender and age are not requirements. Accordingly, this actor can be assigned as an understudy for the role of William (contestant in men’s three-legged race) only if the actor playing Shelly is male, and can be assigned as an understudy for the role of Jessica Smith (teen daughter of the Smith family) only if the actor playing Shelly is both female and a youngster. Accordingly, the casting director could cast the actor playing the Shelly character as the understudy to the William or Jessica Smith characters only by first considering the attributes the actor possesses beyond the attributes required for their assigned role. When the actor playing the Shelly character is male, then he can be assigned to understudy the William character, and when a female youngster, can be assigned to understudy the Jessica Smith character. This is analogous to a specializing cast between reference variables; it may be valid, but it is only valid when the actor actually has the traits required for the role into which the actor would be cast.

ABAP Language Support for Inheritance

The object-oriented extensions to the ABAP language accommodate inheritance in the following ways:

  • By providing a root base class – object – from which all other classes implicitly inherit

  • By supporting the concept of single inheritance , where a class indicates its immediate parent class

  • By providing class member visibility specifically intended to be accessed only by the parent class and any child classes inheriting from it

  • By insuring that constructors at every abstraction level along the inheritance hierarchy are invoked in the proper sequence

  • By providing the capability to override the method implementation provided by a parent class

  • By providing an overriding method implementation the ability to invoke the implementation provided by the parent class

  • By enabling both classes and methods to be defined as final

  • By enabling both classes and methods to be defined as abstract

  • By supporting both generalizing and specializing casting assignments

The indication of inheritance is placed on the definition statement for the class that does the inheriting:

class child_class definition inheriting from parent_class [options].
  o
  o
  o
endclass.
class child_class implementation.
  o
  o
  o
endclass.

When a class does not explicitly indicate a parent class from which it inherits, it implicitly inherits from class object.

A parent class can make members available to itself and any inheriting classes by defining these members in the protected visibility section of the parent class:

class parent_class definition.
  public section.
    o
    o
  protected section.
    [types ...]
    [constants ...]
    [[class-]data ...]
    [[class-]methods ...]
    o
    o
endclass.

The instance constructor of an inheriting class requires an explicit call to the constructor of its parent class. Until that point, the instance constructor can work with static members, parameters defined in the signature of the instance constructor, and any local variables defined within the instance constructor method, but has no access to instance members. It is only after the call to the constructor of its parent class that instance members become available:

  o
  o
  method constructor.
    o
    (at this point, only static members, parameters and local
     variables are available)
    o
    call method super->constructor [exporting ...].
    o
    (at this point, instance members are now avaiable)
    o
  endmethod.

A method implementation provided by a parent class can be overridden by the child class, optionally invoking the implementation of the parent class as part of its own processing:

class child_class definition inheriting from parent_class.
  public section.
    methods name_of_parent_class_public_method redefinition.
    o
    o
  protected section.
    methods name_of_parent_class_protected_method redefinition.
    o
    o
endclass.
class child_class implementation.
  method name_of_parent_class_public_method.
    o
    o
    [call method super-> name_of_parent_class_public_method.]
    o
    o
  endmethod.
  method name_of_parent_class_protected_method.
    o
    o
    [call method super-> name_of_parent_class_protected_method.]
    o
    o
  endmethod.
  o
  o
  o
endclass.

A class can indicate that it does not allow inheritors:

class childless_class definition final.

A parent class can indicate that it does not allow inheriting classes to redefine one or more of its methods:

class parent_class definition.
  public section.
    methods public_method final.
    o
    o
  protected section.
    methods protected_method final.
    o
    o
endclass.

A parent class can indicate that it cannot be instantiated:

class parent_class definition abstract.

A parent class can indicate that it does not provide an implementation for one or more of its methods, thus requiring the child class to provide an implementation:

class parent_class definition abstract.
  public section.
    methods public_method abstract.
    o
    o
  protected section.
    methods protected_method abstract.
    o
    o
endclass.

A casting assignment may be made from a reference variable of a more specialized class to a reference variable of a more generalized class just like any other type of assignment:

class generalized_class definition.
  o
  o
class specialized_class definition inheriting from generalized_class.
  o
  o
  data this_generalized_item type ref to generalized_class.
  data this_specialized_item type ref to specialized_class.
  o
  o
  this_generalized_item = this_specialized_item.

A casting assignment may be made from a reference variable of a more generalized class to a reference variable of a more specialized class by using the move-cast operator on the assignment statement and by embedding the assignment statement within a try-endtry block having a catch statement to intercept an exception of type cx_sy_move_cast_error:

class generalized_class definition.
  o
  o
class specialized_class definition inheriting from generalized_class.
  o
  o
  data this_generalized_item type ref to generalized_class.
  data this_specialized_item type ref to specialized_class.
  o
  o
  try.
    o
    o
    this_specialized_item ?= this_generalized_item.
    o
    o
  catch cx_sy_move_cast_error.
    o
    o
   endtry .

Orienting Ourselves After Having Traversed Inheritance

So, we have traveled yet further along the path from Procedureton to Objectropolis. Now, having completed our traversal through the object-oriented district known as Inheritance, we are familiar with its principles and can speak the language spoken by the residents in this district. Contrary to what we found with Abstraction, with Inheritance we have no recognizable guideposts or familiar terrain; everything here is new. This is a completely different landscape, having no counterpart in our home town of Procedureton. Nonetheless, we are now as familiar with this district as the residents who live here and we are capable of navigating our way around Inheritance as skilfully as those who were raised here.

Refer again to the chart in Appendix A, illustrating the comparison between function groups and classes of how each one facilitates the capabilities of the principles of object-oriented programming. Row 15 shows how these two programming formats support the principles of inheritance. We can see that function groups have no support for this principle; it is unique to classes.

Summary

In this chapter, we became more familiar with the object-oriented concept of Inheritance. We learned how classes can be arranged into a hierarchy where the definition of a class can be supplemented by content provided by a class from which it inherits, enabling us to reuse components previously defined when defining new components, and though child classes are aware of their parent classes, parent classes are unaware of any child classes they may have. We learned that although there are two types of inheritance (single and multiple) available to object-oriented environments, the ABAP language supports only single inheritance. We also learned how to override behavior implementations provided by a superclass, and that this is applicable only to instance behaviors. In addition, we are familiar with the concept of abstract methods, which require implementations supplied by subclasses, and how to indicate that a method of a class may not be overridden. Also covered were the effects inheritance has upon member visibility, constructor methods, maintenance, and some of the dangers of using inheritance indiscriminately. A significant concept presented here is the distinction between the static type and the dynamic type aspects applicable to every reference variable, and how these aspects govern the movement of values between fields defined as instance references. Also covered was the concept of casting, with attention given to the distinction between generalizing casts and specializing casts and how to use them properly. Finally, we were presented with the postal and cinematic metaphors to help grasp the concepts associated with the static type and the dynamic type aspects of reference variables.

Inheritance Exercises

Refer to Chapter 5 of the functional and technical requirements documentation (see Appendix B) for the accompanying ABAP exercise programs associated with this chapter. Take a break from reading the book at this point to reinforce what you have read by changing and executing the corresponding exercise programs. The exercise programs associated with this chapter are those in the 103 series: ZOOT103A through ZOOT103D.

Footnotes

2 We will see a more formal method by which to establish this inheritance relationship in diagrams in the chapter covering the Unified Modeling Language.

3 Although the Internet is full of discussion threads on the topic of whether or not static methods can be or should be able to be overridden, it seems that none of the more common object-oriented languages providing a static realm support this capability.

4 As we will see later, the Singleton design pattern is an example of a class marked as final.

5 Technically, a child class inherits everything from its parent class, but those members defined in the parent class with private visibility will not be visible to the child class.

6 As we shall see later, a reference variable does not always need to be associated with a type of class, but for now we will keep it simple and limit the discussion to class references.

7 Steve McConnell, Code Complete: A practical handbook of software construction, 2nd edition, Microsoft Press, 2004, p. 92

8 We will cover the technique of composition in more detail in the section on Design Patterns.

9 Shallow inheritance hierarchies avoid what is known as the yo-yo problem.

10 There are many tales of programmers having created deep inheritance structures that became unmanageable during subsequent maintenance cycles; this may account for why it has fallen out of favor among scholars.

11 See “The diamond problem” section at www.geeksforgeeks.org/multiple-inheritance-in-c/ .

12 Preceding entries paraphrased from www.astronoo.com/en/gould-belt.html .

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset