Gyan Factory

Gyan Factory
SAP Technical Project Support

Tuesday, February 16, 2016

Case Study: Observer Design Pattern Usage

Recently, at one of my client’s place, I have to design a screen with lot of ALVs. When I started designing, I didn’t pay much of the attention to the fact that there could be a dependency between main ALVs and other sub-ALVs. Later in the design phase, I understood that this is a great opportunity to implement Observer Design Pattern and make maintenance easy and fast.
If you haven’t yet read the basics of Observer, I would strongly suggest to read ABAP Objects Design Patterns – Observer.
The requirement was to have a screen with one main ALV and 2 sub ALVs. Later on the requirement changed and we had to add another 2 sub ALVs. At that point of time, I decided to use Observer to be able to react to any new requirement and refresh all Sub ALVs.
To be able to explain the problem easily, I have created the reduced version of the requirement and its before and after solution. For demo purpose, we’ll create 1 main ALV and 2 Sub ALV. 2 Sub ALVs should be refreshed with new data whenever we double-click on main ALV.

Before Observer

UML

Lets see the UML for this.
I have created a public method REFRESH_KIDS( ) in the class LCL_MAIN_APP. I call the method, from the main ALV, LCL_T100_ALV. I call the REFRESH( ) method of each sub ALV object.
In order to make it happen, I had to do this things:
  1. Define private Attribute for each sub ALV in the main class LCL_MAIN_APP
  2. Create a public method REFRESH_KIDS
  3. Call method REFRESH( ) of each subALV in the method REFRESH_KIDS
  4. Call method REFRESH_KIDS within the DOUBLE CLICK event handler for the main ALV
This translates into: Whenever I need to add another ALV, add an attribute in LCL_MAIN_APP, add into the REFRESH_KIDS method.

Code Lines

The code lines for this demo. You need to create a normal screen 0100 and also create a PF-STATUS ZOBS.
REPORT  znp_dp_wo_observer.
*&---------------------------------------------------------------------*
*& Purpose: Demo Without Observer. Refresh is achieved using a method
*& Author : Naimesh Patel
*&---------------------------------------------------------------------*

DATAv_ok_code TYPE sy-ucomm.

*=== Class Definitions
CLASS lcl_t100_alv DEFINITION.
  PUBLIC SECTION.
    METHODSgenerate_alv IMPORTING io_cont TYPE REF TO cl_gui_container.
  PRIVATE SECTION.
    DATAo_salv TYPE REF TO cl_salv_table.
    DATAt_t100 TYPE STANDARD TABLE OF t100.
    METHODShandle_double_click
              FOR EVENT double_click OF cl_salv_events_table
              IMPORTING row column.
ENDCLASS.                    "lcl_t100_alv DEFINITION
*
CLASS lcl_sub_alv DEFINITION ABSTRACT.
  PUBLIC SECTION.
    METHODSgenerate_alv ABSTRACT IMPORTING io_cont TYPE REF TO cl_gui_container.
    METHODSrefresh ABSTRACT IMPORTING iwa_t100 TYPE t100.
  PROTECTED SECTION.
    DATAo_salv TYPE REF TO cl_salv_table.
    TYPES:
      BEGIN OF ty_info,
        key TYPE char30,
        value TYPE char50,
      END   OF ty_info.
    DATAt_info TYPE STANDARD TABLE OF ty_info.

ENDCLASS.                    "lcl_sub_alv DEFINITION
*
CLASS lcl_t100_details DEFINITION INHERITING FROM lcl_sub_alv.
  PUBLIC SECTION.
    METHODSgenerate_alv REDEFINITION.
    METHODSrefresh REDEFINITION.
ENDCLASS.                    "lcl_t100_details DEFINITION
*
CLASS lcl_t100a_info DEFINITION INHERITING FROM lcl_sub_alv.
  PUBLIC SECTION.
    METHODSgenerate_alv REDEFINITION.
    METHODSrefresh REDEFINITION.
ENDCLASS.                    "lcl_t100a_info DEFINITION
*
CLASS lcl_main_app DEFINITION.
  PUBLIC SECTION.
    CLASS-METHODSrun.
    CLASS-METHODSrefresh_kids IMPORTING iwa_t100 TYPE t100.

  PRIVATE SECTION.
*.... no observer, thus we need to have them as attributes
    CLASS-DATAlo_t100_details TYPE REF TO lcl_t100_details.
    CLASS-DATAlo_t100a_info TYPE REF TO lcl_t100a_info.

ENDCLASS.                    "lcl_main_app DEFINITION

*
CLASS lcl_t100_alv IMPLEMENTATION.
  METHOD generate_alv.
    SELECT FROM t100 INTO TABLE t_t100 UP TO ROWS
      WHERE sprsl sy-langu
      AND arbgb '00'.
    SELECT FROM t100 APPENDING TABLE t_t100 UP TO ROWS
      WHERE sprsl sy-langu
      AND arbgb '01'.

    TRY.
        cl_salv_table=>factory(
          EXPORTING
            r_container  io_cont
          IMPORTING
            r_salv_table o_salv
          CHANGING
            t_table      t_t100 ).
      CATCH cx_salv_msg.
    ENDTRY.

    DATAlo_events TYPE REF TO cl_salv_events_table.
    lo_events o_salv->get_event).
    SET HANDLER handle_double_click FOR lo_events.

    o_salv->display).

  ENDMETHOD.                    "generate_alv
  METHOD handle_double_click.
    DATAlv_index TYPE i.
    DATAwa_t100 TYPE t100.
    lv_index row.
    IF lv_index IS INITIAL.
      lv_index 1.
    ENDIF.
    READ TABLE t_t100 INTO wa_t100 INDEX lv_index.

    "Refresh SubALVs
    lcl_main_app=>refresh_kidswa_t100 ).
  ENDMETHOD.                    "HANDLE_DOUBLE_CLICK
ENDCLASS.                    "lcl_t100_alv IMPLEMENTATION
*
CLASS lcl_t100_details IMPLEMENTATION.
  METHOD generate_alv.

    TRY.
        cl_salv_table=>factory(
          EXPORTING
            r_container  io_cont
          IMPORTING
            r_salv_table o_salv
          CHANGING
            t_table      t_info ).
      CATCH cx_salv_msg.
    ENDTRY.

    o_salv->display).
  ENDMETHOD.                    "generate_alv
  METHOD refresh.

    CLEAR t_info.

*   select data
    DATAlwa_info LIKE LINE OF t_info.
    lwa_info-key 'Language'.
    lwa_info-value iwa_t100-sprsl.
    APPEND lwa_info TO t_info.

    lwa_info-key 'Application Area'.
    lwa_info-value iwa_t100-arbgb.
    APPEND lwa_info TO t_info.

    lwa_info-key 'Message number'.
    lwa_info-value iwa_t100-msgnr.
    APPEND lwa_info TO t_info.

    lwa_info-key 'Message Text'.
    lwa_info-value iwa_t100-text.
    APPEND lwa_info TO t_info.

    o_salv->refresh).
  ENDMETHOD.                    "refresh

ENDCLASS.                    "lcl_t100_details IMPLEMENTATION
*
CLASS lcl_t100a_info IMPLEMENTATION.
  METHOD generate_alv.
    TRY.
        cl_salv_table=>factory(
          EXPORTING
            r_container  io_cont
          IMPORTING
            r_salv_table o_salv
          CHANGING
            t_table      t_info ).
      CATCH cx_salv_msg.
    ENDTRY.
    o_salv->display).
  ENDMETHOD.                    "generate_alv
  METHOD refresh.

*   select data
    DATAlwa_t100a TYPE t100a.
    DATAlwa_info LIKE LINE OF t_info.

    SELECT SINGLE FROM t100a
      INTO lwa_t100a WHERE arbgb iwa_t100-arbgb.

    CLEAR t_info.
    lwa_info-key 'Application Area'.
    lwa_info-value lwa_t100a-arbgb.
    APPEND lwa_info TO t_info.

    lwa_info-key 'MASTERLAN'.
    lwa_info-value lwa_t100a-masterlang.
    APPEND lwa_info TO t_info.

    lwa_info-key 'Description'.
    lwa_info-value lwa_t100a-stext.
    APPEND lwa_info TO t_info.

    o_salv->refresh).
  ENDMETHOD.                    "refresh
ENDCLASS.                    "lcl_t100a_info IMPLEMENTATION
*
CLASS lcl_main_app IMPLEMENTATION.
  METHOD run.
    DATAdocking TYPE REF TO cl_gui_docking_container.
    DATAlo_t100_alv TYPE REF TO lcl_t100_alv.

    CREATE OBJECT docking
      EXPORTING
        repid sy-repid
        dynnr '0100'
        side  docking->dock_at_top
        ratio 90.

    DATAlo_split  TYPE REF TO cl_gui_splitter_container,
          lo_ch1    TYPE REF TO cl_gui_container,
          lo_split2 TYPE REF TO cl_gui_splitter_container,
          lo_ch2    TYPE REF TO cl_gui_container.
    DATAlo_temp TYPE REF TO cl_gui_container.

    CREATE OBJECT lo_split
      EXPORTING
        parent  docking
        rows    2
        columns 1.

    lo_split->set_row_height(
        id      1
        height  45
         ).

*   alv 1
    lo_temp lo_split->get_container(
                          row       1
                          column    ).


    CREATE OBJECT lo_t100_alv.
    lo_t100_alv->generate_alvlo_temp ).

*   alv bottom
    lo_ch1 lo_split->get_container(
               row    2
               column 1
               ).

    CREATE OBJECT lo_split2
      EXPORTING
        parent  lo_ch1
        rows    1
        columns 3.


    lo_temp lo_split2->get_container(
                          row       1
                          column    ).

    CREATE OBJECT lo_t100_details.
    lo_t100_details->generate_alvlo_temp ).

    lo_temp lo_split2->get_container(
                          row       1
                          column    ).


    CREATE OBJECT lo_t100a_info.
    lo_t100a_info->generate_alvlo_temp ).

  ENDMETHOD.                    "run

  METHOD refresh_kids.

    " Any new ALV, we need to put the REFRESH method call here
    lo_t100_details->refreshiwa_t100 ).
    lo_t100a_info->refreshiwa_t100 ).

  ENDMETHOD.                    "refresh_kids

ENDCLASS.                    "lcl_main_app IMPLEMENTATION

START-OF-SELECTION.
  lcl_main_app=>run).
  CALL SCREEN 0100.

*
MODULE user_command_0100 INPUT.
  CASE v_ok_code.
    WHEN 'BACK'.
      LEAVE PROGRAM.
  ENDCASE.
ENDMODULE.                 " USER_COMMAND_0100  INPUT

*
MODULE status_0100 OUTPUT.
  SET PF-STATUS 'ZOBS'.
ENDMODULE.                 " STATUS_0100  OUTPUT

After Observer

UML

Here is the updated UML.
To achieve Observer using event, I made these changes:
  • Removed the method REFRESH_KIDS as well as the attributes from the Class LCL_MAIN_APP
  • Created a new EVENT, REFRESH_DETAILS with data record of a row on which double click event occurred
  • Replaced methods REFRESH( ) in subALVs with methods ON_REFRESH_DETAILS as a event handler of the event REFRESH_DETAILS of the main ALV.
  • Registering the event handlers for the SubALVs while generating the ALVs in the method RUN of the LCL_MAIN_APP.

Code Lines

Code lines with Observer. You need to create a normal screen 0100 and also create a PF-STATUS ZOBS.
REPORT  znp_dp_with_observer.
*&---------------------------------------------------------------------*
*& Purpose: Demo With Observer. Refresh is achieved via Event
*& Author : Naimesh Patel
*&---------------------------------------------------------------------*

DATAv_ok_code TYPE sy-ucomm.

*=== Class Definitions
CLASS lcl_t100_alv DEFINITION.
  PUBLIC SECTION.
    METHODSgenerate_alv IMPORTING io_cont TYPE REF TO cl_gui_container.
    EVENTSrefresh_details EXPORTING value(wa_t100TYPE t100.
  PRIVATE SECTION.
    DATAo_salv TYPE REF TO cl_salv_table.
    DATAt_t100 TYPE STANDARD TABLE OF t100.
    METHODShandle_double_click
              FOR EVENT double_click OF cl_salv_events_table
              IMPORTING row column.
ENDCLASS.                    "lcl_t100_alv DEFINITION
*
CLASS lcl_sub_alv DEFINITION ABSTRACT.
  PUBLIC SECTION.
    METHODSgenerate_alv ABSTRACT IMPORTING io_cont TYPE REF TO cl_gui_container.
  PROTECTED SECTION.
    METHODSon_refresh_details ABSTRACT
      FOR EVENT refresh_details OF lcl_t100_alv
      IMPORTING wa_t100.
    DATAo_salv TYPE REF TO cl_salv_table.
    TYPES:
      BEGIN OF ty_info,
        key TYPE char30,
        value TYPE char50,
      END   OF ty_info.
    DATAt_info TYPE STANDARD TABLE OF ty_info.
ENDCLASS.                    "lcl_sub_alv DEFINITION
*
CLASS lcl_t100_details DEFINITION INHERITING FROM lcl_sub_alv.
  PUBLIC SECTION.
    METHODSgenerate_alv REDEFINITION.
  PROTECTED SECTION.
    METHODSon_refresh_details REDEFINITION.
ENDCLASS.                    "lcl_t100_details DEFINITION
*
CLASS lcl_t100a_info DEFINITION INHERITING FROM lcl_sub_alv.
  PUBLIC SECTION.
    METHODSgenerate_alv REDEFINITION.
  PROTECTED SECTION.
    METHODSon_refresh_details REDEFINITION.
ENDCLASS.                    "lcl_t100a_info DEFINITION
*
CLASS lcl_main_app DEFINITION.
  PUBLIC SECTION.
    CLASS-METHODSrun.
ENDCLASS.                    "lcl_main_app DEFINITION

*==== Class Implementations
CLASS lcl_t100_alv IMPLEMENTATION.
  METHOD generate_alv.
    SELECT FROM t100 INTO TABLE t_t100 UP TO ROWS
      WHERE sprsl sy-langu
      AND arbgb '00'.
    SELECT FROM t100 APPENDING TABLE t_t100 UP TO ROWS
      WHERE sprsl sy-langu
      AND arbgb '01'.

    TRY.
        cl_salv_table=>factory(
          EXPORTING
            r_container  io_cont
          IMPORTING
            r_salv_table o_salv
          CHANGING
            t_table      t_t100 ).
      CATCH cx_salv_msg.
    ENDTRY.

    DATAlo_events TYPE REF TO cl_salv_events_table.
    lo_events o_salv->get_event).
    SET HANDLER handle_double_click FOR lo_events.

    o_salv->display).
  ENDMETHOD.                    "generate_alv
  METHOD handle_double_click.
    DATAlv_index TYPE i.
    DATAwa_t100 TYPE t100.
    lv_index row.
    IF lv_index IS INITIAL.
      lv_index 1.
    ENDIF.
    READ TABLE t_t100 INTO wa_t100 INDEX lv_index.

*   Let Dependent ALVs know that double click happend
*   and refresh themshelves
    RAISE EVENT refresh_details
      EXPORTING wa_t100 wa_t100.

  ENDMETHOD.                    "HANDLE_DOUBLE_CLICK
ENDCLASS.                    "lcl_t100_alv IMPLEMENTATION

*
CLASS lcl_t100_details IMPLEMENTATION.
  METHOD generate_alv.

    TRY.
        cl_salv_table=>factory(
          EXPORTING
            r_container  io_cont
          IMPORTING
            r_salv_table o_salv
          CHANGING
            t_table      t_info ).
      CATCH cx_salv_msg.
    ENDTRY.

*   Registering to catch the event REFRESH_DETAILS
    SET HANDLER on_refresh_details FOR ALL INSTANCES.

    o_salv->display).
  ENDMETHOD.                    "generate_alv
  METHOD on_refresh_details.

    CLEAR t_info.

*   select data
    DATAlwa_info LIKE LINE OF t_info.
    lwa_info-key 'Language'.
    lwa_info-value wa_t100-sprsl.
    APPEND lwa_info TO t_info.

    lwa_info-key 'Application Area'.
    lwa_info-value wa_t100-arbgb.
    APPEND lwa_info TO t_info.

    lwa_info-key 'Message number'.
    lwa_info-value wa_t100-msgnr.
    APPEND lwa_info TO t_info.

    lwa_info-key 'Message Text'.
    lwa_info-value wa_t100-text.
    APPEND lwa_info TO t_info.

    o_salv->refresh).
  ENDMETHOD.                    "refresh

ENDCLASS.                    "lcl_t100_details IMPLEMENTATION
*
CLASS lcl_t100a_info IMPLEMENTATION.
  METHOD generate_alv.
    TRY.
        cl_salv_table=>factory(
          EXPORTING
            r_container  io_cont
          IMPORTING
            r_salv_table o_salv
          CHANGING
            t_table      t_info ).
      CATCH cx_salv_msg.
    ENDTRY.

*   Registering to catch the event REFRESH_DETAILS
    SET HANDLER on_refresh_details FOR ALL INSTANCES.

    o_salv->display).
  ENDMETHOD.                    "generate_alv
  METHOD on_refresh_details.

*   select data
    DATAlwa_t100a TYPE t100a.
    DATAlwa_info LIKE LINE OF t_info.

    SELECT SINGLE FROM t100a
      INTO lwa_t100a WHERE arbgb wa_t100-arbgb.

    CLEAR t_info.
    lwa_info-key 'Application Area'.
    lwa_info-value lwa_t100a-arbgb.
    APPEND lwa_info TO t_info.

    lwa_info-key 'MASTERLAN'.
    lwa_info-value lwa_t100a-masterlang.
    APPEND lwa_info TO t_info.

    lwa_info-key 'Description'.
    lwa_info-value lwa_t100a-stext.
    APPEND lwa_info TO t_info.

    o_salv->refresh).
  ENDMETHOD.                    "refresh
ENDCLASS.                    "lcl_t100a_info IMPLEMENTATION
*
CLASS lcl_main_app IMPLEMENTATION.
  METHOD run.

    DATA docking TYPE REF TO cl_gui_docking_container.
    DATAlo_t100_alv     TYPE REF TO lcl_t100_alv.
    DATAlo_t100_details TYPE REF TO lcl_sub_alv.
    DATAlo_t100a_info   TYPE REF TO lcl_sub_alv.

    CREATE OBJECT docking
      EXPORTING
        repid sy-repid
        dynnr '0100'
        side  docking->dock_at_top
        ratio 90.

    DATAlo_split  TYPE REF TO cl_gui_splitter_container,
          lo_ch1    TYPE REF TO cl_gui_container,
          lo_split2 TYPE REF TO cl_gui_splitter_container,
          lo_ch2    TYPE REF TO cl_gui_container.
    DATAlo_temp TYPE REF TO cl_gui_container.

    CREATE OBJECT lo_split
      EXPORTING
        parent  docking
        rows    2
        columns 1.

    lo_split->set_row_height(
        id      1
        height  45
         ).

*   MAIN ALV
    lo_temp lo_split->get_container(
                          row       1
                          column    ).


    CREATE OBJECT lo_t100_alv.
    lo_t100_alv->generate_alvlo_temp ).

*   Bottom Container
    lo_ch1 lo_split->get_container(
               row    2
               column 1
               ).

    CREATE OBJECT lo_split2
      EXPORTING
        parent  lo_ch1
        rows    1
        columns 3.


*   SUB ALV 1
    lo_temp lo_split2->get_container(
                          row       1
                          column    ).
    CREATE OBJECT lo_t100_details TYPE lcl_t100_details.
    lo_t100_details->generate_alvlo_temp ).

*   SUB ALV2
    lo_temp lo_split2->get_container(
                          row       1
                          column    ).
    CREATE OBJECT lo_t100a_info TYPE lcl_t100a_info.
    lo_t100a_info->generate_alvlo_temp ).

  ENDMETHOD.                    "run
ENDCLASS.                    "lcl_main_app IMPLEMENTATION

START-OF-SELECTION.
  lcl_main_app=>run).
  CALL SCREEN 0100.

*
MODULE user_command_0100 INPUT.
  CASE v_ok_code.
    WHEN 'BACK'.
      LEAVE PROGRAM.
  ENDCASE.
ENDMODULE.                 " USER_COMMAND_0100  INPUT

*
MODULE status_0100 OUTPUT.
  SET PF-STATUS 'ZOBS'.
ENDMODULE.                 " STATUS_0100  OUTPUT

Conclusion

After implementing Observer, it is very easy to add any new SubALV in this functionality. We can simply inherit the subclass and register the event handling for that. Of course, we need to instantiate the ALV class itself but apart from that, we don’t need to modify anything in existing object.

No comments:

Post a Comment