Application Development Blog Posts
Learn and share on deeper, cross technology development topics such as integration and connectivity, automation, cloud extensibility, developing at scale, and security.
cancel
Showing results for 
Search instead for 
Did you mean: 
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.

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 8
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.
4 Comments