© James E. McDonough 2017

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

6. Polymorphism

James E. McDonough

(1)Pennington, New Jersey, USA

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

Long before the advent of object-oriented programming, the word polymorphism applied predominately to the fields of biology and chemistry. It means many (poly) shapes (morph) quality (ism). Applied to computer programming, it is

  • “…the provision of a single interface to entities of different types.”1

It enables us to elicit different behaviors from the same class method by controlling some aspect of the call to the method. Support for polymorphism in object-oriented languages is usually provided using one or both of the following alternatives:

  • Static polymorphism

  • Dynamic polymorphism

Whereas we have seen the word dynamic used in the context of dynamic type, as described in the chapter on inheritance, we have seen the word static used in many more contexts, primarily in opposition to the word instance, as with static and instance members as well as in opposition to the word dynamic, as with static and dynamic type, and now with static and dynamic polymorphism. Such overuse of a word runs the risk of causing confusion to those trying to learn the underlying concepts.2

Static Polymorphism

Static polymorphism is made available through a technique known as method overloading. With method overloading, a class contains multiple definitions for the same method name, but each of these definitions has a unique signature with a corresponding unique implementation. Each unique signature is different from those of the other methods of the same name either by the number of parameters or by the type of parameter.

Here is a simple example of pseudocode showing a text formatter class that has more than one definition for method advancePointer:

class textFormatter
  method advancePointer(a type integer, b type integer)
    return a + b
  method advancePointer(a type integer, b type integer, c type integer)
    return a + b + c

One of these advancePointer methods defines a signature accepting two integers; the other defines a signature accepting three integers. Accordingly, the advancePointer method is overloaded with two different signatures.

So how do we know which implementation will be executed when we invoke the advancePointer method? This is determined by the number of parameters supplied by the caller of the method, as illustrated in the following snippet of pseudocode:

  o
  o
  spacesBetweenWords              type integer
  positionOfThisWord              type integer
  lengthOfThisWord                type integer
  o
  o
  positionOfNextWord = advancePointer(positionOfThisWord
                                      lengthOfThisWord
                                      spacesBetweenWords)
  o
  o
  positionOfComma    = advancePointer(positionOfThisWord
                                      lengthOfThisWord)

The calculation for positionOfNextWord invokes method advancePointer with three integer parameters, and so it will invoke the implementation of the advancePointer method accepting three integer parameters, whereas the calculation for positionOfComma, having provided only two integer parameters, will invoke the implementation of the advancePointer method accepting two integer parameters.

With static polymorphism , the programmer explicitly controls the method invocation by specifying matching parameters for an overloaded method. That is, the selection of overloaded method implementation to execute is made by providing on the method call whatever number and type of parameters match that variation of method signature. This means invocations of these overloaded methods are statically bound when the program is compiled, and will not change until the code is subsequently changed to alter this arrangement and recompiled. It is known as static polymorphism because the selected behavior cannot be changed during execution.

Many object-oriented languages support static polymorphism, among them C++, C#, and Java. ABAP does not support static polymorphism.3

Dynamic Polymorphism

Dynamic polymorphism is made available through a process known as dynamic dispatch. With dynamic dispatch, the implementation to be invoked when a method is called is determined during execution. This is achieved by accessing the implementation of the method offered by the actual instance through which the method call is being made; that is, it’s made using the dynamic type of the corresponding object reference variable.

Dynamic polymorphism relies on method overriding, whereby a subclass provides its own implementation to override a method inherited from a parent class. Accordingly, it is not applicable to static methods for the simple reason that static method implementations cannot be overridden in subclasses.

Dynamic polymorphism occurs when a call is made to a method of an instance addressed through an object reference variable for which its static type and dynamic type are different. Indeed, dynamic dispatch is used to facilitate invoking methods of instances even when the static and dynamic types of the object reference variable are the same, but dynamic polymorphism is at work only when they are different.

To illustrate how this works, let’s use a class inheritance hierarchy describing a small collection of watercrafts, as shown in Figure 6-1. In this hierarchy, classes motorBoat and sailBoat both inherit from class boat.

A447555_1_En_6_Fig1_HTML.jpg
Figure 6-1. Watercraft hierarchy

Class boat is represented by the pseudocode shown in Listing 6-1.

Listing 6-1. Pseudocode for Class boat
abstract class boat
  public abstract method start
  public abstract method turnLeft
  public abstract method turnRight
  public abstract method stop
endclass

Class boat offers only four methods, all of which are abstract, meaning that the responsibility to provide an implementation for these methods is incumbent upon the inheriting subclasses; that is, each subclass needs to override these four methods. At first glance, we might consider the idea of inherited methods to exemplify the notion of reusing code, but we soon realize that the absence of implementations for these methods seems to nullify this advantage since we are required to provide our own implementations for every inherited method in every subclass inheriting from boat. Indeed, at this point we might be wondering why even bother having a boat class when it provides so little in the way of code reusability. This is a very good question, and if you find yourself pondering it at this moment, then perhaps the next few paragraphs will provide a convincing justification that this might actually be a good idea.

Class motorBoat is represented by the pseudocode shown in Listing 6-2.

Listing 6-2. Pseudocode for Class motorBoat
class motorBoat inherits from boat
  public method start
    engage propeller
  endmethod
  public method turnLeft
    rotate steering wheel counterclockwise
  endmethod
  public method turnRight
    rotate steering wheel clockwise
  endmethod
  public method stop
    disengage propeller
  endmethod
endclass

Perhaps by now you may be wondering why even bother having class motorBoat inherit from class boat. After all, it would seem at this point to make virtually no difference whether class motorBoat were to include or omit the indication that it inherits from class boat since it would work either way; indeed, the relationship seems frivolous and arbitrary, offering very little value to class motorBoat.

Class sailBoat is represented by the pseudocode shown in Listing 6-3.

Listing 6-3. Pseudocode for Class sailBoat
class sailBoat inherits from boat
  public method start
    raise sail
  endmethod
  public method turnLeft
    push tiller to right
  endmethod
  public method turnRight
    push tiller to left
  public endmethod
  method stop
    lower sail
  endmethod
endclass

Here again we see little reason for class sailBoat to inherit from class boat since class sailBoat gains very little from the inheritance, and represents yet another example of a frivolous and arbitrary relationship, offering very little value to class sailBoat.

Now let’s use these classes in the pseudocode describing a marina, shown in Listing 6-4.

Listing 6-4. Pseudocode for Class marina
class marina
  public method launchMotorBoat
    thisMotorBoat                 type class of motorBoat
    create new instance of motorBoat into thisMotorBoat
    call method maneuverMotorBoat(thisMotorBoat)
  endmethod
  public method launchSailBoat
    thisSailBoat                  type class of sailBoat
    create new instance of sailBoat into thisSailBoat
    call method maneuverSailBoat(thisSailBoat)
  endmethod
  private method maneuverMotorBoat(thisBoat type class of motorBoat)
    invoke behavior start     of thisBoat
    invoke behavior turnLeft  of thisBoat
    invoke behavior turnRight of thisBoat
    invoke behavior stop      of thisBoat
  endmethod
  private method maneuverSailBoat(thisBoat type class of sailBoat)
    invoke behavior start     of thisBoat
    invoke behavior turnLeft  of thisBoat
    invoke behavior turnRight of thisBoat
    invoke behavior stop      of thisBoat
  endmethod
endclass

This further reinforces our skepticism about the need for class boat, which now appears to be completely unnecessary as a parent class to motorBoat and sailBoat.

Notice in Listing 6-4 that for method launchMotorBoat, the static type of variable thisMotorBoat is motorBoat and its dynamic type is also motorBoat according to the next statement creating an instance of motorBoat into it. Furthermore, the next statement after that invokes method maneuverMotorBoat, which is defined with a signature, represented by the parenthetical phrase, accepting a single parameter thisBoat, defined as type class of motorBoat, denoting the static type of the parameter and requiring that the instance the caller sends must be one of class motorBoat or of some class inheriting from motorBoat. During execution, the static types and dynamic types are the same for all of them: motorBoat. Polymorphism does not apply when the static type and dynamic type are the same. This same concept also applies to method launchSailBoat and its call to method maneuverSailBoat, for which, during execution, the static types and dynamic types also are the same for all of them: in this case, sailBoat. Again, no polymorphism applies.

Meanwhile, since the same sequence of maneuvers is being performed for both the motorBoat and the sailBoat, let’s consider whether or not we could consolidate some of this code. Upon closer inspection it is found that methods maneuverMotorBoat and maneuverSailBoat are identical except for the type of class each one accepts in its signature. Method maneuverMotorBoat accepts an instance of class motorBoat while maneuverSailBoat accepts an instance of class sailBoat.

Since, according to the inheritance diagram, motorBoat “is a” boat and sailBoat also “is a” boat, we can consolidate the methods maneuverMotorBoat and maneuverSailBoat into a single method called maneuverBoat defined with a signature accepting an instance of class boat, as shown in Listing 6-5, which describes the reworked marina pseudocode in which the changes required to consolidate the two methods into one are highlighted.

Listing 6-5. Pseudocode for Class marina Where Methods maneuverMotorBoat and maneuverSailBoat Have Been Consolidated into the Single Method maneuverBoat
class marina
  public method launchMotorBoat
    thisMotorBoat                 type class of motorBoat
    create new instance of motorBoat into thisMotorBoat
    call method maneuverBoat(thisMotorBoat)
  endmethod
  public method launchSailBoat
    thisSailBoat                  type class of sailBoat
    create new instance of sailBoat into thisSailBoat
    call method maneuverBoat(thisSailBoat)
  endmethod
  private method maneuverBoat(thisBoat type class of boat)
    invoke behavior start     of thisBoat
    invoke behavior turnLeft  of thisBoat
    invoke behavior turnRight of thisBoat
    invoke behavior stop      of thisBoat
  endmethod
endclass

Whereas the former maneuverMotorBoat and maneuverSailBoat methods had accepted instances of class motorBoat and sailBoat, respectively, method maneuverBoat accepts an instance of class boat, a more generalized level of abstraction than that required with the maneuverMotorBoat and maneuverSailBoat methods. Accordingly, method maneuverBoat can accept an instance of a motorBoat as well as a sailBoat , but it treats each one simply as an instance of boat.

In this reworked example in Listing 6-5, the static type of the parameter thisBoat, defined in the signature for method maneuverBoat, is now type boat. During execution, this method will be passed an instance of a motorBoat by the launchMotorBoat method, and will be passed an instance of a sailBoat by the launchSailBoat method. This means that in both cases the static type of this parameter, boat, and the dynamic type it receives, either motorBoat or sailboat, from one of the calling methods will not be the same. Accordingly, as maneuverBoat invokes the sequence of behaviors (start, turnLeft, turnRight, and stop) of the thisBoat parameter, the actual behavior will depend on whether it is working with an instance of a motorBoat or a sailBoat. For example, its call to the start method of the boat instance represented by the thisBoat parameter will result in engaging the propeller when maneuverBoat is working with an instance of motorBoat , but will result in raising the sail when working with an instance of sailBoat. This is because dynamic dispatch will locate and execute the method implementation associated with the dynamic type of the thisBoat parameter, and illustrates the very essence of polymorphism: different behaviors resulting from different executions of the same fragment of code.

Perhaps now we understand the value conferred upon classes motorBoat and sailBoat by inheriting from class boat. Instances of motorBoat and sailBoat can be regarded simply as instances of boat. When we first saw the definition of class boat in Listing 6-1 we were skeptical of its importance because it offered so little in the way of code reuse . We had similar reactions when we saw how little the boat class improved upon the motorBoat (Listing 6-2) and SailBoat (Listing 6-3) classes, each of which indicated inheriting from boat. We drew this conclusion based solely on the perceived notion that the value for creating a class is in its ability to contribute to code reuse. But now, with the benefits of polymorphism drawn into full focus for us, it becomes clear that there are other reasons for defining classes beyond just the advantages offered by code reusability. Indeed, the power of polymorphism lies in the ability to regard a class instance at an abstraction level more general than the level of abstraction at which it is defined.

To reflect upon how dynamic polymorphism applies to some of the other levels of abstraction we have already explored,

  • A rectangle and an enclosed shape with at least one angle each can be regarded simply as an enclosed shape.

  • A dog and a fox each can be regarded simply as a mammal.

  • A mammal and a reptile each can be regarded simply as an animal.

  • A convertible and a sedan each can be regarded simply as a car.

  • A convertible and a truck each can be regarded simply as a vehicle.

  • A male adult, female adult, male youngster, and female youngster all can be regarded simply as a winner of a pie-eating contest.

This ability to regard an instance of a class as though it were defined at a higher level of abstraction than it actually is defined represents a fundamental characteristic of object-oriented environments, presenting a new dimension to programming and offering a profound advancement in software design beyond the capabilities available to procedural environments .

Eliminating Conditional Logic

Compare the classes shown in the preceding listings with the equivalent procedural pseudosubroutines that might be required for maneuvering boats, shown in Listing 6-6.

Listing 6-6. Procedural Subroutine Code Equivalent to Classes
form start
  case boat_type
    when motor_boat
      engage propeller
    when sail_boat
      raise sail
  endcase
endform


form turn_left
  case boat_type
    when motor_boat
      rotate steering wheel counterclockwise
    when sail_boat
      push tiller to right
  endcase
endform


form turn_right
  case boat_type
    when motor_boat
      rotate steering wheel clockwise
    when sail_boat
      push tiller to left
  endcase
endform


form stop
  case boat_type
    when motor_boat
      disengage propeller
    when sail_boat
      lower sail
  endcase
endform

Each subroutine facilitates a boat maneuvering behavior, and every one of them contains conditional logic for determining the specific action to take based on the type of boat to be maneuvered. The equivalent classes required no conditional logic because each of the classes inheriting from class boat implicitly designated the type of boat to be maneuvered, rendering any checking of the type of boat to maneuver completely unnecessary!4 Accordingly, polymorphism facilitates what Horst Keller and Sascha Krűger refer to as case-less programming.5

Effect of Polymorphism upon Subsequent Maintenance

Frequently we find such procedural processes designed to rely on conditional programming scattered throughout different components, testing some condition for a specific value in order to determine the correct processing to apply at that point, and often this is implemented through a cascade condition, where a sequential series of tests determine which one of a multitude of possible options is the correct one. Such proliferation of conditional logic makes the task of maintenance that much harder when some new feature needs to be implemented.

For the scenario described above, let’s consider for a moment the impact upon the code if we were given a maintenance task to introduce a new type of boat: a row boat. The procedural subroutines would all need to change to facilitate the new type of boat, as shown in Listing 6-7 where differences from Listing 6-6 are highlighted.

Listing 6-7. Procedural Subroutines to Facilitate a New Type of Boat, with Differences from Listing 6-6 Highlighted
form start
  case boat_type
    when motor_boat
      engage propeller
    when sail_boat
      raise sail
    when row_boat
      pull both oars
  endcase
endform


form turn_left
  case boat_type
    when motor_boat
      rotate steering wheel counterclockwise
    when sail_boat
      push tiller to right
    when row_boat
      pull right oar while dragging left oar
  endcase
endform


form turn_right
  case boat_type
    when motor_boat
      rotate steering wheel clockwise
    when sail_boat
      push tiller to left
    when row_boat
      pull left oar while dragging right oar
  endcase
endform


form stop
  case boat_type
    when motor_boat
      disengage propeller
    when sail_boat
      lower sail
    when row_boat
      push both oars
  endcase
endform

In each case we altered the subroutine handling a specific boat maneuvering action to include a new type of boat to maneuver. By contrast, the corresponding object-oriented change requires only the definition of a new class inheriting from class boat, as shown in Listing 6-8.

Listing 6-8. New Class to Handle New Type of Boat
class rowBoat inherits from boat
  public method start
    pull both oars
  endmethod
  public method turnLeft
    push right oar while dragging left oar
  endmethod
  public method turnRight
    push left oar while dragging right oar
  public endmethod
  method stop
    push both oars
  endmethod
endclass

Here the object-oriented version in Listing 6-8 required 14 lines of code, compared with the 8 lines added to the procedural subroutines shown in Listing 6-7. There are those who would point to this difference in new lines and proclaim the procedural programming paradigm superior to object-oriented due to the fewer lines of code required for this maintenance task. However, a closer look reveals other, perhaps more important, benefits with the object-oriented approach:

  • Whereas the extra lines of procedural code were distributed across four different existing subroutines, all of the new code required with the object-oriented approach was contained within a single new class.

  • Whereas the extra lines of procedural code all were conditional statements, none of the object-oriented code required any conditional statements.

Indeed, not only did the object-oriented approach have no conditional logic prior to the maintenance effort, as shown in Listings 6-2 and 6-3, neither was any required for the additional maintenance to implement the new feature as shown in Listing 6-8. Compare this with the procedural approach shown in Listing 6-7, which not only had conditional logic prior to the changes, but resulted in increasing the amount of conditional statements to facilitate the new feature. At some point in the future, the continued maintenance required with the procedural approach would spiral out of control and result in brittle software that is difficult to maintain. This example clearly illustrates the distinction between a procedural design, with its focus on managing the process, and an object-oriented design, with its focus on manging the data.

Using a more fitting example of code we are likely to find in the real world, suppose we are working at Seven Seas Forwarding Company, a corporation facilitating the movement of international cargo via ocean-going vessels. One of the programs of the software package supporting the everyday business activities at this corporation prints shipping paperwork to accompany the cargo to be shipped. Part of preparing this paperwork includes determining the following:

  • The language spoken at the receiving port

  • The cost to ship the cargo

  • Any additional import tariffs imposed by the receiving port country

  • The gross weight of the cargo

  • Container placards

Listing 6-9 shows the pseudocode for a typical cascade condition for determining the language spoken at the receiving port.

Listing 6-9. Example of Cascade Condition for Determining Language
if receivingPort is "Melbourne" or "New Orleans" or ...
  paperworkLanguage = "English"
else
  if receivingPort is "Le Havre" or "Majunga" ...
    paperworkLanguage = "French"
  else
    if receivingPort is "Santos" or "Lisbon" ...
      paperworkLanguage = "Portuguese"
    else
      if receivingPort is ...
        o
        o
        o
execute printPaperwork(paperworkLanguage)

Listing 6-10 shows a cascade condition for determining the unit of measure in which to express the gross weight of the cargo.

Listing 6-10. Example of a Cascade Condition for Determining Applicable Unit of Measure
if receivingCountry is "United States" or "Liberia"
  grossWeightUnit = "pound"
else
  if receivingCountry is "Myanmar"
    grossWeightUnit = "peittha"
  else
    grossWeightUnit = "kilogram"


execute calculateGrossWeight(grossWeightUnit)

Most of us have encountered code exhibiting similar constructions, though often far more complicated than these simple examples, which also could be solved using simple configuration techniques. The problem with such cascading conditions, aside from their difficulty to easily discern as they get longer and longer, is that they are often scattered among different components, and when a change needs to be applied, such as including a receiving port where a new language is spoken or adding a receiving country where a different unit of measure is used, it often requires adjustments to be made in multiple locations . Worse, every one of these changed locations presumably had been working correctly until the new change was introduced, so it now would require retesting every one of these changed locations, triggering all formerly existing conditions along with all the new conditions.

We can avoid this maintenance headache if we create a class that can define the methods required, such as printPaperwork and calculateGrossWeight as shown in the pseudocode of Listings 6-9 and 6-10, but leave the implementation up to subclasses. This way we can instantiate a subclass written specifically to handle the destination port and eliminate all the conditional logic that would otherwise be required in scattered locations, as in the ABAP example shown in Listing 6-11.

Listing 6-11. ABAP Code for a Subclass Supplying Implementation for Abstract Methods
class destination_port definition.
  public section.
    o
    o
    methods      : print_paperwork abstract
                     importing
                       target_language
                         type language
                 , calculate_gross_weight abstract
                     importing
                       weight
                         type quantity
                       gross_weight_unit
                         type weight_unit
                     exporting
                       gross_weight
                         type quantity
                 .
endclass.


class destination_port_new_orleans definition
                                   inheriting from destination_port.
  public section.
    o
    o
    methods      : print_paperwork redefinition
                 , calculate_gross_weight redefinition
                 .
  private section.
    constants    : gross_weight_unit
                                  type mass_unit value 'pound'
                 , paperwork_language
                                  type language  value 'English'
                 .
endclass.
    o
    o

Now we can create an instance of a destination_port_new_orleans into a reference variable of type destination_port, and invoke its methods, which will not require any conditional logic to determine the language spoken at the receiving port, cost to ship the cargo, any additional import tariffs imposed by the receiving port country, gross weight of the cargo, or container placards, because these conditions are implicit in the implementation of the class itself. The only time we need conditional logic is for determining which subclass of class destination_port to instantiate; once instantiated, we no longer need all of the scattered conditional logic otherwise required to determine the associated processing to apply. This means, among other things, we no longer need to change working logic scattered throughout the code just to include one additional receiving port, simplifying not only the task of finding these locations and applying the correct changes there but also avoiding the necessity for testing components that now don’t need to change.

Accordingly, one of the most significant benefits of polymorphism is the way it eliminates the need for introducing more and more conditional programming during maintenance cycles , leaving the code in a state where it can withstand repeated maintenance cycles without requiring excessive effort to understand or change.

ABAP Language Support for Polymorphism

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

  • By accommodating dynamic polymorphism (dynamic dispatch )

  • By supporting abstract methods

  • By providing for redefinition of inherited method implementations

There are no specific statements for facilitating polymorphism.6 It occurs as a consequence of providing implementations for methods of a class that inherits these method definitions from a superclass,7 and then references an instance of the class as though it were an instance of its superclass.

Orienting Ourselves After Having Traversed Polymorphism

We have passed the point of no return on our journey from Procedureton to Objectropolis, and now, having completed our traversal through the object-oriented district known as Polymorphism we are familiar with its principles and now are fluent in the language spoken by its residents. Now, it is onward to Objectropolis or bust!

As we found with Inheritance, Polymorphism also offers no recognizable guideposts or familiar terrain; this is a foreign landscape with 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 as capable as the native population in navigating our way around Polymorphism.

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 16 shows how these two programming formats support the principles of polymorphism. We can see that function groups offer no support for this principle; it also is unique to classes.

Summary

In this chapter, we became more familiar with the object-oriented concept of polymorphism. We learned there are two kinds of polymorphism, static and dynamic, and that only dynamic polymorphism applies to the ABAP language through a process known as dynamic dispatch. We learned that a significant reason for using polymorphism is to reduce the conditional logic we might otherwise require, having the beneficial effect of simplifying our maintenance efforts since such conditional logic no longer becomes scattered across multiple objects.

Polymorphism Exercises

Refer to Chapter 6 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 104 series: ZOOT104A and ZOOT104B.

Footnotes

2 Indeed, we already have seen the word static used as a qualifier to describe encapsulation units, constructors and destructors, classes and members of classes, realm, reference variable type, and design constraints.

3 This is one of a few examples where the implementation of object-oriented capabilities in ABAP deviates from the Java model.

4 One could argue that we could define these subroutines specific to the type of boat to be maneuvered, such as start_motor_boat and turn_sail_boat_right, but that only shifts to the caller the location of the conditional logic to make the determination of both the type of boat and its maneuver.

5 Horst Keller and Sascha Krűger, ABAP Objects: An Introduction to Programming SAP Applications, Addison Wesley, 2002, p.294.

6 We might consider the word redefinition, qualifying a methods statement of a subclass definition, as a word enabling polymorphic behavior; however, this qualifier may also be used in the absence of polymorphism.

7 We shall see in a subsequent chapter that such method definitions can be provided in ways other than just through inheritance.

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

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