Additional Blogs by Members
cancel
Showing results for 
Search instead for 
Did you mean: 
jwood_bowdark
Active Participant

In my previous OO Programming with ABAP Objects: Inheritance, we learned about inheritance relationships. As you may recall, the term inheritance is used to describe a specialization relationship between related classes in a given problem domain. Here, rather than reinvent the wheel, we define a new class in terms of a pre-existing one. The new class (or subclass) is said to inherit from the existing class (or parent class). Most of the time, when people talk about inheritance, they focus their attention on code reuse and the concept of implementation inheritance. Implementation inheritance is all about reducing redundant code by leveraging inherited components to implement new requirements rather than starting all over from scratch.

One aspect of inheritance relationships that sometimes gets swept under the rug is fact that subclasses also inherit the interface of their parent classes. This type of inheritance is described using the term interface inheritance. Interface inheritance makes it possible to use instances of classes in an inheritance hierarchy interchangeably – a concept that is referred to as polymorphism. In this blog, we will explore the idea of polymorphism and show you how to use it to develop highly flexible code.

What is Polymorphism?

As you may recall from my last OO Programming with ABAP Objects: Inheritance, one of the litmus tests for identifying inheritance relationships is to ask yourself whether or not a prospective subclass fits into an “Is-A” relationship with a given parent class. For example, a circle is a type of shape, so defining a “Circle” class in terms of an abstract “Shape” class makes sense. Looking beyond the obvious benefits of reusing any implementation provided in the “Shape” class, let’s think about what this relationship means from an interface perspective. Since the “Circle” class inherits all of the public attributes/methods of the “Shape” class, we can interface with instances of this class in the exact same way that we interface with instances of the “Shape” class. In other words, if the “Shape” class defines a public method called “draw()”, then so does the “Circle” class. Therefore, the code required to call this method on instances of either class is exactly the same even if the underlying implementation is very different.

The term polymorphism literally means “many forms”. From an object-oriented perspective, the term is used to describe a language feature that allows you to use instances of classes belonging to the same inheritance hierarchy interchangeably. This idea is perhaps best explained with an example. Getting back to our “Shape” discussion, let’s think about how we might design a shape drawing program. One possible implementation of the shape drawing program would be to create a class that defines methods like “drawCircle()”, “drawSquare()”, etc. Another approach would be to define a generic method called “draw()” that uses conditional statements to branch the logic out to modules that are used to draw a circle, square, etc. In either case, there is work involved whenever a new shape is added to the mix. Ideally, we would like to decouple the drawing program from our shape hierarchy so that the two can vary independently. We can achieve this kind of design using polymorphism.

In a polymorphic design, we can create a generic method called “draw()” in our drawing program that receives an instance of the “Shape” class as a parameter. Since subclasses of “Shape” inherit its interface, we can pass any kind of shape to the “draw()” method and it can turn around and use that shape instance’s “draw()” method to draw the shape on the screen. In this way, the drawing program is completely ignorant of the type of shape it is handling; it simply delegates the drawing task to the shape instance. This is as it should be since the Shape class already knows how to draw itself. Furthermore, as new shapes are introduced into the mix, no changes would be required to the drawing program so long as these new shapes inherit from the abstract “Shape” class.

This generic approach to programming is often described using the term design by interface. The basic concept here is to adopt a component-based architecture where each component clearly defines the services (i.e. interface) they provide. These interfaces make it easy for components to be weaved together into larger assemblies. Here, notice that we haven’t said anything about how these components are implemented. As long as the components implement the services described in their interface – it really doesn’t matter how they are implemented. From an object-oriented perspective, this means that we can swap out a given object for another so long as they share the same interface. Of course, in order to do so, we need to be able to perform type casts and dynamic method calls.

Type Casting and Dynamic Binding

Most of the time, whenever we talk about the type of an object reference variable in ABAP Objects, we are talking about its static type. The static type of an object reference variable is the class type used to define the reference variable:

DATA: lr_oref TYPE REF TO zcl_shape.

An object reference variable also has a dynamic type associated with it. The dynamic type of an object reference variable is the type of the current object instance that it refers to. Normally, the static and dynamic type of an object reference variable will be the same. However, it is technically possible for an object reference variable to point to an object that is not an instance of the class type used to define the object reference. For example, notice how we are assigning an instance of the ZCL_CIRCLE subclass to the lr_shape object reference variable (whose static type is the parent class ZCL_SHAPE) in the code excerpt below.

DATA: lr_shape  TYPE REF TO zcl_shape,
      lr_circle TYPE REF TO zcl_circle.

CREATE OBJECT lr_shape.
CREATE OBJECT lr_circle.
lr_shape = lr_circle.

This kind of assignment is not possible without a type cast. Of course, you can’t perform a type cast using just any class; the source and target object reference variables must be compatible (e.g., their static types must belong to the same inheritance hierarchy). In the example above, once the assignment is completed, the dynamic type of the lr_shape reference variable will be the ZCL_CIRCLE class. Therefore, at runtime, when a method call such as “lr_shape->draw( )” is performed, the ABAP runtime environment will use the dynamic type information to bind the method call with the implementation provided in the ZCL_CIRCLE class.

The type cast above is classified as a narrowing cast (or upcast) as we are narrowing the access scope of the referenced ZCL_CIRCLE object to the components defined in the ZCL_SHAPE superclass. It is also possible to perform a widening cast (or downcast) like this:

DATA: lr_shape  TYPE REF TO zcl_shape,
      lr_circle TYPE REF TO zcl_circle.
CREATE OBJECT lr_shape TYPE zcl_circle.
lr_circle ?= lr_shape.

In this case, we are using the TYPE addition to the CREATE OBJECT statement to create an instance of class ZCL_CIRCLE and assign its reference to the lr_shape object reference variable. Then, we use the casting operator (“?=”) to perform a widening cast when we assign the lr_shape object reference to lr_circle. The casting operator is something of a precaution in many respects as widening casts can be dangerous. For instance, in this contrived example, we know that we are assigning an instance of ZCL_CIRCLE to an object reference variable of that type. On the other hand, if the source object reference were a method parameter, we can’t be sure that this is the case. After all, someone could pass in a square to the method and cause all kinds of problems since class ZCL_CIRCLE may well define circle-specific methods that cannot be executed against an instance of class ZCL_SQUARE.

Implementing Polymorphism in ABAP

Now that you have a feel for how type casting works in ABAP Objects, let’s see how to use it to implement a polymorphic design in ABAP. The example code below defines a simple report called ZPOLYTEST that defines an abstract base class called LCL_ANIMAL and two concrete subclasses: LCL_CAT and LCL_DOG. These classes are used to implement a model of the old “See-n-Say” toys manufactured by Mattel, Inc. This model is realized in the form of a local class called LCL_SEE_AND_SAY. If you have never played with a See-n-Say before, its interface is very simple. In the center of the toy is a wheel with pictures of various animals. When a child can positions a lever next to a given animal, the toy will produce the sound of that animal. In order to make the See-n-Say generic, we define the interface of the “play()” method to receive an instance of class LCL_ANIMAL. However, in the START-OF-SELECTION event, you’ll notice that we create instances of LCL_CAT and LCL_DOG and pass them to the See-n-Say. Here, we didn’t have to perform an explicit type cast as narrowing type casts are performed implicitly in method calls. Furthermore, since the LCL_CAT and LCL_DOG classes inherit the methods “get_type()” and “speak()” from class LCL_ANIMAL, we can use instances of them in the LCL_SEE_AND_SAY generically.

REPORT zpolytest.

CLASS lcl_animal DEFINITION ABSTRACT.
  PUBLIC SECTION.
    METHODS: get_type ABSTRACT,
             speak ABSTRACT.
ENDCLASS.

CLASS lcl_cat DEFINITION
              INHERITING FROM lcl_animal.
  PUBLIC SECTION.
    METHODS: get_type REDEFINITION,
             speak REDEFINITION.
ENDCLASS.

CLASS lcl_cat IMPLEMENTATION.
  METHOD get_type.
    WRITE: 'Cat'.
  ENDMETHOD.

  METHOD speak.
    WRITE: 'Meow'.
  ENDMETHOD.
ENDCLASS.

CLASS lcl_dog DEFINITION
              INHERITING FROM lcl_animal.
  PUBLIC SECTION.
    METHODS: get_type REDEFINITION,
             speak REDEFINITION.
ENDCLASS.

CLASS lcl_dog IMPLEMENTATION.
  METHOD get_type.
    WRITE: 'Dog'.
  ENDMETHOD.

  METHOD speak.
    WRITE: 'Bark'.
  ENDMETHOD.
ENDCLASS.

CLASS lcl_see_and_say DEFINITION.
  PUBLIC SECTION.
    CLASS-METHODS:
      play IMPORTING im_animal
                TYPE REF TO lcl_animal.
ENDCLASS.

CLASS lcl_see_and_say IMPLEMENTATION.
  METHOD play.
    WRITE: 'The'.
    CALL METHOD im_animal->get_type.
    WRITE: 'says'.
    CALL METHOD im_animal->speak.
  ENDMETHOD.
ENDCLASS.

START-OF-SELECTION.
  DATA: lr_cat TYPE REF TO lcl_cat,
        lr_dog TYPE REF TO lcl_dog.

  CREATE OBJECT lr_cat.
  CREATE OBJECT lr_dog.

  CALL METHOD lcl_see_and_say=>play
    EXPORTING
      im_animal = lr_cat.
  NEW-LINE.
  CALL METHOD lcl_see_and_say=>play
    EXPORTING
      im_animal = lr_dog.

As mentioned earlier, one of the primary advantages of using polymorphism in a design like this is that we can easily plug in additional animals without having to change anything in class LCL_SEE_AND_SAY. For instance, if we want to add a pig to the See-n-Say, we just create a class LCL_PIG that inherits from LCL_ANIMAL and then we can start passing instances of this class to the “play()” method of class LCL_SEE_AND_SAY.

Conclusions and Next Steps

Hopefully by now you are starting to see the benefit of implementing object-oriented designs. In many respects, polymorphism represents one of the major payoffs for investing the time to create an object-oriented design. However, as you have seen, polymorphism doesn't happen by accident. In order to get there, you need to pay careful attention to the definition of a class' public interface, make good use of encapsulation techniques, and model your class relationships correctly.

If the concept of polymorphism seems familiar, it could be that you’ve seen examples of this in other areas of SAP. Perhaps the most obvious example here would be with “Business Add-Ins” (or BAdIs). In my next blog, we will look at how BAdIs use interfaces to implement polymorphic designs. Interfaces are an important part of any object-oriented developer’s toolbag; making it possible to extend a class into different dimensions.

14 Comments