Get Started with ABAP Programming on SAP BTP

Using Encapsulation To Ensure Consistency

Objectives
After completing this lesson, you will be able to:

After completing this lesson, you will be able to:

  • Explain Encapsulation
  • Define and Use Constructors

Data Encapsulation

In object-oriented programming, an object corresponds to a real-life object such as an employee, a vehicle, or, in our case, a flight connection. It has attributes that describe it, in the case of a flight connection, those are the carrier id and the flight number.

Now let us consider what happens when a program that is using this object wants to provide values for these attributes. It is obvious that only those combinations of carrier id and flight number should be accepted that correspond to a flight connection in the real world.

In object orientation, the client program should not be able to change the attribute values directly. Instead, it should need to call a method to perform this task. The method, which comes with the object, can then check whether the combination of carrier id and flight number is valid and, if this is not the case - reject the change.

As developers of class lcl_connection, we can ensure the use of method set_attributes( ) by making attributes carrier_id and connection_id private, or at least read only.

This concept is called data encapsulation; The information about the flight connection is managed by the connection object itself and cannot be manipulated by anyone else. This ensures that no other point in the program can bypass the check of the consistency - either knowingly or unknowingly. This is one of the major advantages of object-orientation.

In object oriented programming, it is recommended to use data encapsulation as much as possible!

With the public attributes we defined by now, it was possible to read and change the values of the attributes anywhere inside and outside the class.

In order to restrict access to the attributes you have two options:

  1. Keep the DATA statement or CLASS-DATA statement in the public section and add addition READ-ONLY. In doing so, write access to the attribute is forbidden outside of the class, but read access is still possible.
    Note
    Addition READ-ONLY is only allowed in the public section of a class.
  2. Move the DATA statement or CLASS-DATA statement from the public section to one of the other visibility sections, for example the private section. In doing so, read access and write access to the attribute is forbidden outside of the class.
    Hint
    ADT offers a quick fix to change the visibility of an attribute. To use it place the cursor in the attribute name, press Ctrl + 1, and choose Make <attribute> private.

How To Use Private Attributes

Use The Instance Constructor

By making your attributes private - or read-only, at least - you can ensure that the client program uses the available set_attributes( ) method to set the values for attributes carrier_id and connection_id.

But there is still potential for inconsistencies:

  • You cannot force the program to call set_attributes( ) for each new instance. As a result, there can be instances with initial values for carrier_id and connection_id.
  • The client program can call set_attributes( ) several times for the same instance. This should also not be possible.

What you need is a technique to enforce non-initial values during instantiation and to prevent later changes.

To solve these problems, object-oriented programming languages use constructor methods.

The runtime system calls the constructor automatically when you create a new instance of the class, but you may not call it explicitly. Thus the constructor is guaranteed to run once and once only for each instance that you create.

From a syntax perspective a constructor method has following properties:

  • a public instance method with the reserved name constructor
  • may have importing parameters, for example to obtain starting values for the attributes of the new instance
  • may raise exceptions

Note:

In ABAP, it is not possible to define more than one constructor method in the same class.

Tip:

In ADT, you can use a quick fix to generate a constructor method. To use this quick fix proceed as follows:

  1. Either in the definition or implementation part, place the cursor in the class name and press Ctrl + 1.
  2. From the list of available quick fixes, choose Generate constructor
  3. On the dialog window that appears, select the attributes you want to initialize with the constructor and choose Finish

Once you generated the constructor you can adjust its definition and implementation to your needs.

The example shows the constructor of class lcl_connection. The generated definition contains an importing parameter for each of the attributes carrier_id and connection_id. The generated parameters have the same names as the related attributes.

The generated part of the implementation contains a value assignment for each of the attributes with the related importing parameter on the right. Self-reference me-> is needed to distinguish the attributes from the parameters of identical name.

The example shows some manual additions to the generated constructor of class lcl_connection.

In order to reject the creation of instances with initial values, a consistency check was added to the implementation and a RAISING clause to the definition of the constructor.

Because the constructor is executed once and only once for each new instance of class lcl_connection, the constructor implementation is the perfect place to increment static attribute conn_counter.

When you instantiate a class that has a constructor, the system calls the constructor method automatically. If the constructor has importing parameters, you pass them in the NEW expression exactly as you would pass parameters to a normal method.

Note
A constructor can have only importing parameters. Keyword EXPORTING is neither needed nor allowed in the NEW expression.

Attention:

If the constructor has exceptions, you must ensure that you catch them by enclosing the instantiation in a TRY… CATCH...ENDTRY block. If an constructor raises an exception, control returns immediately to the program containing the NEW expression. In this case, you do not receive a new instance of the class.

How To Define An Instance Constructor

Use The Static Constructor

While the instance constructor is called once when each instance of a class is created, you will sometimes need to perform actions once only for the entire class. For this purpose ABAP allows you to define a static constructor, also known as class constructor.

The runtime system calls the static constructor once only when the class is addressed for the first time during the execution of a program.

The first addressing of a class could be one of the following:

  • First instantiation of the class
  • First call of a static method
  • First access to a public static attribute
Note
This list is not complete. There are other actions (related to inheritance) that can cause a class to be addressed for the first time.

A typical use case for the static constructor is the dynamic initialization of static attributes with non-initial values. Therefore it is important that the runtime calls the static constructor before creating the instance, calling the static method, or addressing the static attribute.

From a syntax perspective a constructor method has following properties:

  • A public static method with the reserved name class_constructor
  • Without parameters or exceptions
Note
A static constructor must not have a signature because it is impossible to tell exactly when a class will be addressed for the first time.
Hint

In ADT, you can use a quick fix to generate a class constructor method. To use this quick fix proceed as follows:

  1. Either in the definition or implementation part, place the cursor in the class name and press Ctrl + 1.
  2. From the list of available quick fixes, choose Generate class constructor

How To Debug The Execution Of Constructors

Use Private Attributes and Constructors

Task 1: Preparation

Steps

  1. If you finished the previous exercise Define and Implement Methods, create a copy of your global class ZCL_##_METHODS, and name the copy ZCL_##_CONSTRUCTORS, where ## is your group number. Then skip the rest of this task.

    1. In the Project Explorer view on the left, expand your package.

    2. Expand node Source Code LibraryClasses

    3. Right-click the name of the class you want to copy and choose Duplicate.

    4. In the Name field, enter the name ZCL_##_CONSTRUCTORS, where ## is your group number.

    5. Choose Next.

    6. Select your transport request and choose Finish.

  2. If you did not finish the previous exercise, create a new global class ZCL_##_CONSTRUCTORS, where ## is your group number. Ensure that the class implements the interface IF_OO_ADT_CLASSRUN.

    1. Choose FileNewABAP Class.

    2. Enter the name of your package in the Package field. In the Name field, enter the name ZCL_##_CONSTRUCTORS, where ## is your group number. Enter a description.

    3. In the Interfaces group box, choose Add.

    4. Enter IF_OO_ADT_CLASSRUN. When the interface appears in the hit list, double-click it to add it to the class definition.

    5. Choose Next.

    6. Select your transport request and choose Finish.

  3. Copy the following code between METHOD if_oo_adt_classrun~main. and ENDMETHOD. on the Global Class tab:

    Code snippet
    
    DATA connection TYPE REF TO lcl_connection.
        DATA connections TYPE TABLE OF REF TO lcl_connection.
    
    * First Instance
    **********************************************************************
        connection = NEW #(  ).
    
        TRY.
            connection->set_attributes(
              EXPORTING
                i_carrier_id    = 'LH'
                i_connection_id = '0400'
            ).
    
            APPEND connection TO connections.
    
          CATCH cx_abap_invalid_value.
            out->write( `Method call failed` ).
        ENDTRY.
    
    *    connection->carrier_id    = 'LH'.
    *    connection->connection_id = '0400'.
    *    APPEND connection TO connections.
    
    * Second instance
    **********************************************************************
    
        connection = NEW #(  ).
    
        TRY.
            connection->set_attributes(
              EXPORTING
                i_carrier_id    = 'AA'
                i_connection_id = '0017'
            ).
    
            APPEND connection TO connections.
    
          CATCH cx_abap_invalid_value.
            out->write( `Method call failed` ).
        ENDTRY.
    
    *    connection->carrier_id    = 'AA'.
    *    connection->connection_id = '0017'.
    *    APPEND connection TO connections.
    
    * Third instance
    **********************************************************************
        connection = NEW #(  ).
    
        TRY.
            connection->set_attributes(
              EXPORTING
                i_carrier_id    = 'SQ'
                i_connection_id = '0001'
            ).
    
            APPEND connection TO connections.
    
          CATCH cx_abap_invalid_value.
            out->write( `Method call failed` ).
        ENDTRY.
    
    *    connection->carrier_id    = 'SQ'.
    *    connection->connection_id = '0001'.
    *
    *    APPEND connection TO connections.
    
    
    * Output
    **********************************************************************
    
        LOOP AT connections INTO connection.
    
          out->write( connection->get_output( ) ).
    
        ENDLOOP.
    
      ENDMETHOD.
    
    Copy code
    1. Navigate to the Global Class tab and insert the source code between METHOD if_oo_adt_classrun~main. and ENDMETHOD..

  4. Copy the following code to the Local Types tab:

    Code snippet
    
    CLASS lcl_connection DEFINITION.
    
      PUBLIC SECTION.
    
        DATA carrier_id    TYPE /dmo/carrier_id.
        DATA connection_id TYPE /dmo/connection_id.
    
        CLASS-DATA conn_counter TYPE i.
    
        METHODS set_attributes
          IMPORTING
            i_carrier_id    TYPE /dmo/carrier_id
            i_connection_id TYPE /dmo/connection_id
          RAISING
            cx_abap_invalid_value.
    
        METHODS get_output
          returning
            value(r_output) type string_table.
    
      PROTECTED SECTION.
      PRIVATE SECTION.
    
    ENDCLASS.
    
    CLASS lcl_connection IMPLEMENTATION.
    
      METHOD get_output.
    
        APPEND |------------------------------| TO r_output.
        APPEND |Carrier:     { carrier_id    }| TO r_output.
        APPEND |Connection:  { connection_id }| TO r_output.
    
    
      ENDMETHOD.
    
      METHOD set_attributes.
       IF i_carrier_id IS INITIAL OR i_connection_id IS INITIAL.
          RAISE EXCEPTION TYPE cx_abap_invalid_value.
        ENDIF.
    
        carrier_id    = i_carrier_id.
        connection_id = i_connection_id.
    
      ENDMETHOD.
    
    ENDCLASS.
    
    Copy code
    1. Navigate to the Local Types tab and insert the source code.

Task 2: Make Attributes Private

Change the visibility of attributes carrier_id and connection_id to enforce the usage of methods set_attributes( ) and get_output( ).

Steps

  1. Set the visibility of attribute carrier_id to private using a quick fix.

    1. Go to the declaration of attribute carrier_id in local class lcl_connection.

    2. Place the cursor on carrier_id and press Ctrl + 1.

    3. Double-click the suggestion Make carrier_id private.

    4. Check that the declaration of attribute carrier_id has moved to the private section.

  2. Set the visibility of attribute connection_id to private using a quick fix.

    1. Go to the declaration of attribute connection_id in local class lcl_connection.

    2. Place the cursor on connection_id and press Ctrl + 1.

    3. Double-click the suggestion Make connection_id private.

    4. Check that the declaration of attribute connection_id has moved to the private section.

Task 3: Create Instance Constructor

Replace public method set_attributes( ) with an instance constructor to ensure that the attributes are set during the creation of a new instance and that they are not changed afterward.

Steps

  1. Comment the definition and implementation of method set_attributes( ).

    1. Switch to the Local Types tab.

    2. Select all lines that belong to the METHODS set_attributes statement, including the line with the period sign (.).

    3. Press Ctrl + < to add a star sign (*) in front of each selected line.

    4. Select all lines that belong to the implementation of method set_attributes( ) including the corresponding ENDMETHOD. statement and press Ctrl + < again.

  2. Add an instance constructor to the local class lcl_connection using a quick fix. Ensure that the constructor has importing parameters corresponding to attributes carrier_id and connection_id.

    1. Place the cursor on the name of the class and press Ctrl + 1.

    2. Double-click on Generate constructor.

    3. In the dialog box, ensure that carrier_id and connection_id are selected and choose Finish.

  3. Extend the generated constructor definition. Add exception CX_ABAP_INVALID_VALUE to the definition of method constructor( ).

    1. Navigate to the generated definition of method constructor( ).

    2. At the end of the METHODS statement, before the period sign, add RAISING CX_ABAP_INVALID_VALUE .

  4. Extend the generated constructor implementation. If either of the importing parameters is initial, raise exception CX_ABAP_INVALID_VALUE.

    1. Navigate from the definition to the implementation of method constructor, for example, by placing the cursor on constructor and pression F3.

    2. After METHOD constructor, before the generated value assignments, add the following code.

      Code snippet
      
         IF carrier_id IS INITIAL OR connection_id IS INITIAL.
            RAISE EXCEPTION TYPE cx_abap_invalid_value.
          ENDIF.
      
      Copy code
  5. In the constructor implementation, add a statement to increase the value of static attribute conn_counter by one. Make sure the statement is only executed if no exception was raised.

    1. After the generated value assignments, before ENDMETHOD., add conn_counter = conn_counter + 1..

Task 4: Use the Constructor

Adjust the instantiation of class lcl_connection to supply the parameters of the instance constructor and handle the exception.

Steps

  1. In method if_oo_adt_classrun~main( ), go to the NEW #( ) expression for the first instance of lcl_connection.

    1. Switch to the Global Class tab.

    2. Find the first line with connection = NEW #( )..

  2. In the NEW expression, supply the import parameters of constructor( ).

    Hint
    You can copy the parameter passing from the call of method set_attributes( ) for this instance.
    1. Adjust the NEW expression as follows:

      Code snippet
      
         connection = NEW #( 
                             i_carrier_id    = 'LH'
                             i_connection_id = '0400' 
                           ).
      
      Copy code
  3. Remove or comment the call of method set_attributes( ).

    1. Select all lines that belong to the connection->set_attributes( ... ). statement, including the line with the closing bracket and the period sign (.).

    2. Press Ctrl + < to add a star sign (*) in front of each selected line.

  4. Move the instance creation into the TRY block of the exception handling.

    1. Move the TRY. statement up, to before the instance creation.

    2. Your first instance creation should now look like this:

      Code snippet
      
      TRY.
          connection = NEW #( 
                              i_carrier_id    = 'LH'
                              i_connection_id = '0400'
                            ).
         
      *        connection->set_attributes(
      *          EXPORTING
      *            i_carrier_id    = 'LH'
      *            i_connection_id = '0400'
      *        ).
      
              APPEND connection TO connections.
      
            CATCH cx_abap_invalid_value.
              out->write( `Method call failed` ).
          ENDTRY.
      
      Copy code
  5. Repeat the previous steps for the other instances of your local class.

    1. The implementation of method if_oo_adt_classrun~main( ) should then look like this:

      Code snippet
      
      METHOD if_oo_adt_classrun~main.
      
          DATA connection TYPE REF TO lcl_connection.
          DATA connections TYPE TABLE OF REF TO lcl_connection.
      
      * First Instance
      **********************************************************************
          TRY.
          connection = NEW #( 
                              i_carrier_id    = 'LH'
                              i_connection_id = '0400'
                            ).
         
      *        connection->set_attributes(
      *          EXPORTING
      *            i_carrier_id    = 'LH'
      *            i_connection_id = '0400'
      *        ).
      
              APPEND connection TO connections.
      
            CATCH cx_abap_invalid_value.
              out->write( `Method call failed` ).
          ENDTRY.
      
      * Second instance
      **********************************************************************
          TRY.
          connection = NEW #(  
                              i_carrier_id    = 'AA'
                              i_connection_id = '0017'
                            ).
              APPEND connection TO connections.
      
            CATCH cx_abap_invalid_value.
              out->write( `Method call failed` ).
          ENDTRY.
      
      * Third instance
      **********************************************************************
          TRY.
          connection = NEW #(  
                              i_carrier_id    = 'SQ'
                              i_connection_id = '0001'
                            ).
              APPEND connection TO connections.
      
            CATCH cx_abap_invalid_value.
              out->write( `Method call failed` ).
          ENDTRY.
      
      * Output
      **********************************************************************
          LOOP AT connections INTO connection.
            out->write( connection->get_output( ) ).
          ENDLOOP.
        ENDMETHOD.
      
      Copy code
  6. Activate the class. Execute it and debug the instantiation.

    1. Press Ctrl + F3 to activate the class.

    2. Press F9 to run the class.

Save progress to your learning plan by logging in or creating an account