Monday, 6 April 2020

Understanding Polymorphism

In this blog-post i want to give you a deep understanding of polymorphism with interfaces.

The object-orientated memory allocation model


In context of object orientation the terms “class”, “object” and “instantiation” are very common. But what does this really mean? Let’s consider a simple class:

CLASS flight_plan DEFINITION.

  PUBLIC SECTION.

    METHODS constructor
      IMPORTING
        airline_id TYPE s_carrid
        connection_id TYPE s_conn_id.

    METHODS set_city_from
      IMPORTING
        city TYPE s_cit_from
      RAISING
        cx_city_without_airport.

    METHODS get_city_from
      RETURNING VALUE(result) TYPE s_from_cit.

  PRIVATE SECTION.
    DATA: airline_id TYPE s_carr_id,
          connection_id TYPE s_conn_id,
          time_zone_city_from TYPE s_tzone,
          city_from TYPE s_from_cit.
ENDCLASS.

CLASS flight_plan IMPLEMENTATION.

  METHOD constructor.
    me->airline_id = airline_id.
    me->connection_id = connection_id.
  ENDMETHOD.

  METHOD set_city_from.
    IF city_has_airport( city_from ) = abap_false.
      RAISE EXCEPTION TYPE cx_city_without_airport.
    ENDIF.
    me->time_zone_city_from = get_time_zone_city( city_from ).
    me->city_from = city_from.
  ENDMETHOD.

  METHOD get_city_from.
    result = city_from.
  ENDMETHOD.

ENDCLASS.

SAP ABAP Polymorphism, SAP ABAP Guides, SAP ABAP Learning, SAP ABAP Certifications

Instantiation is done old fashioned with the CREATE OBJECT-Statement or since release 7.40 with the new operator.

DATA: flight_plan_o TYPE REF TO flight_plan.

* old fashioned
CREATE OBJECT flight_plan
  EXPORTING
    airline_id = '..'
    connection_id = '...'.

* new fashioned
DATA(flight_plan_n) = NEW flight_plan( airline_id = '..'
  connection_id = '...' ).

What happens at instantiation?


The system allocates a area in the ram big enough to store all attributes of class flight_plan and gives us a reference (the so called object), which is pointing to this area.
The following table shows the size in an unicode-system, which this area at least needs to cover:

 Attribute Length in bytes
 airline_id 6
 connection_id
 city_from 40 
 time_zone_city_from 12 
 Sum 66 

At least 66 bytes are needed to hold an instance of class flight_plan.

The variables flight_plan_o and flight_plan_n are references. Spoken in a metaphor the attributes are stored inside a parcel and the object is the label outside of the parcel.

Copy an object


What do you expect? Which city is printed at the end of this code-sample?

DATA copy_flight_plan TYPE REF TO flight_plan.

DATA(ba_flight_plan) = NEW flight_plan( airline_id = 'BA'
  connection_id = '400' ).
copy_flight_plan = ba_flight_plan.

ba_flight_plan->set_city_from( 'London' ).
copy_flight_plan->set_city_from( 'Frankfurt' ).

WRITE ba_flight_plan->get_city_from( ).

‘Frankfurt’ is printed out. Why? When copying a object, the attributes aren’t copied. The 66 bytes, which were allocated at instantiation of class flight_plan, are just allocated one time. A copy of the object doesn’t allocate an area with the same size again. It just creates a copy of the label, not of the parcel, if we stick to our metaphor.

So the variables ba_flight_plan and copy_flight_plan are pointing to one memory area.
This is the huge difference to procedurally programming. When implementing a similar code sample with procedurally ABAP, ‘London’ instead of Frankfurt is printed out.

TYPES: BEGIN OF flight_plan,
  airline_id TYPE s_carr_id,
  connection_id TYPE s_conn_id,
  city_from TYPE s_from_cit,
  time_zone_city_from TYPE s_tzone,
END OF flight_plan.

DATA(ba_flight_plan) = VALUE flight_plan( airline_id = 'BA' connection_id = '400' city_from = 'London' ).
DATA(copy_flight_plan) = ba_flight_plan.

copy_flight_plan-city_from = 'Frankfurt'.

WRITE ba_flight_plan-city_from.

The reason is, that structures are copied by value. Every field of the structure ba_flight_plan is copied to the structure copy_flight_plan.

Objects as function-module or method parameters


Let’s modify the code-sample a bit. The instance of class flight_plan is now passed to an function-module parameter.

FUNCTION SET_CITY_FRANKFURT.
* IMPORTING
*   VALUE(flight_plan_instance) TYPE REF TO flight_plan

  flight_plan_instance->set_city_from( 'Frankfurt' ).

ENDFUNCTION.

Executing the following code-sample gives us again the city ‘Frankfurt’.

DATA(ba_flight_plan) = NEW flight_plan( airline_id = 'BA'
  connection_id = '400' ).

ba_flight_plan->set:city_from( 'London' ).
CALL FUNCTION 'SET_CITY_FRANKFURT'
  EXPORTING
    flight_plan_instance = ba_flight_plan.

WRITE ba_flight_plan->get_city_from( ).

The reason is the same as described before. Just a reference pointing to the class flight_plan is copied, not the attributes.

Interface


Let’s do some more abstractions and wrap the class flight_plan with an interface (cx_city_validation should be an superclass of cx_city_without_airport):

INTERFACE if_flight_plan.

  METHODS set_city_from
    IMPORTING
      city TYPE s_from_cit
    RAISING
      cx_city_validation.

  METHODS get_city_from
    RETURNING VALUE(result) TYPE s_from_cit.

ENDINTERFACE.

CLASS flight_plan DEFINITION.

  PUBLIC SECTION.

    METHODS constructor
      IMPORTING
        airline_id TYPE s_carrid
        connection_id TYPE s_conn_id.

    INTERFACES if_flight_plan.

  PRIVATE SECTION.
    DATA: airline_id TYPE s_carr_id,
          connection_id TYPE s_conn_id,
          time_zone_city_from TYPE s_tzone,
          city_from TYPE s_from_cit.
ENDCLASS.

CLASS flight_plan IMPLEMENTATION.

  METHOD constructor.
    me->airline_id = airline_id.
    me->connection_id = connection_id.
  ENDMETHOD.

  METHOD if_flight_plan~set_city_from.
    IF city_has_airport( city_from ) = abap_false.
      RAISE EXCEPTION TYPE cx_city_without_airport.
    ENDIF.
    me->time_zone_city_from = get_time_zone_city( city_from ).
    me->city_from = city_from.
  ENDMETHOD.

  METHOD if_flight_plan~get_city_from.
    result = city_from.
  ENDMETHOD.

ENDCLASS.

The parameter-definition flight_plan_instance of function-module SET_CITY_FRANKFURT is changed:

FUNCTION SET_CITY_FRANKFURT.
* IMPORTING
*   VALUE(flight_plan_instance) TYPE REF TO if_flight_plan

  flight_plan_instance->set_city_from( 'Frankfurt' ).

ENDFUNCTION.

The following variable represents a reference for an class-instance, which must implement the interface if_flight_plan.

DATA flight_plan_abstract_instance TYPE REF TO if_flight_plan.

When instantiating the class flight_plan, the reference pointing to the allocated memory area can be directly casted to flight_plan_abstract_instance. Casted means, only the methods declared in the interface if_flight_plan are now callable. When you see the reference as a memory-area-label, the only thing you can see on this label are the methods declared in interface if_flight_plan.

The following code-sample gives us again the city ‘Frankfurt’.

DATA: flight_plan_abstract_instance TYPE REF TO if_flight_plan.

flight_plan_abstract_instance = NEW flight_plan( airline_id = 'BA' connection_id = '400' ).

ba_flight_plan->set:city_from( 'London' ).
CALL FUNCTION 'SET_CITY_FRANKFURT'
  EXPORTING
    flight_plan_instance = flight_plan_abstract_instance.

WRITE flight_plan_abstract_instance->get_city_from( ).

Now we cannot only wrap the class flight_plan with the interface if_flight_plan, we can create other classes, which implement the interface if_flight_plan in an other manner. The principle is the same. Once instantiated, a memory area for all attributes of the class is allocated.

A label in form of an reference variable is created for this memory area and the only thing a consumer can see on this label are the methods declared in the interface if_flight_plan. This is how polymorphism with interfaces works.

No comments:

Post a Comment