software, software.sap

A General Purpose ABAP Multiton Class

In this post, I will share a general purpose class covering the multiton design pattern. By implementing a simple interface, you can add multiton functionality to your existing classes.

Multiton is a performance oriented design pattern. It is based on the idea of caching and re-using objects corresponding to the same key. For each object key (such as a vendor number), a static object instance of a class (such as a vendor class) is kept in a central location. Whenever a client requests an object corresponding the key, the existing object is returned instead of creating a new one. This approach reduces the memory footprint due to the decreased number of objects, and avoids the performance cost to re-create objects having the same key.

A typical multiton class would have the following skeleton structure.

CLASS zcl_vendor DEFINITION
  PUBLIC FINAL CREATE PRIVATE.

  PUBLIC SECTION.

    DATA gv_lifnr TYPE lifnr READ-ONLY

    CLASS-METHODS get_instance
      IMPORTING !iv_lifnr TYPE lifnr
      RETURNUNG VALUE(ro_obj) TYPE REF TO zcl_vendor.

  PRIVATE SECTION.

   TYPES:
      BEGIN OF t_multiton,
        lifnr TYPE lifnr,
        obj TYPE REF TO zcl_vendor,
      END OF t_multiton,

      tt_multiton TYPE HASHED TABLE OF t_multiton
        WITH UNIQUE KEY primary_key COMPONENTS lifnr.

    CLASS-DATA gt_multiton TYPE tt_multiton.

    METHODS constructor
      IMPORTING
        !iv_lifnr TYPE lifnr.

  PROTECTED SECTION.

ENDCLASS.

 
CLASS zcl_vendor IMPLEMENTATION.

  METHOD constructor.
    “ Check if IV_LIFNR exists in LFA1 and raise error if not
    gv_lifnr = iv_lifnr.
  ENDMETHOD.

  METHOD get_instance.

    ASSIGN gt_multiton[ KEY primary_key
      COMPONENTS lifnr = iv_lifnr ]
      TO FIELD-SYMBOL(<ls_multiton>).

    IF sy-subrc NE 0.

      “ Check if IV_LIFNR exists in LFA1 and raise error if not
      DATA(ls_multiton) = VALUE t_multiton( lifnr = iv_lifnr ).
      ls_multiton-obj = NEW #( iv_lifnr ).
      INSERT ls_multiton 
        INTO TABLE gt_multiton
        ASSIGNING <ls_multiton>.

    ENDIF.

    ro_obj = <ls_multiton>-obj.

  ENDMETHOD.
 

ENDCLASS.

When ZCL_VENDOR=>GET_INSTANCE( ‘12345’ ) is called for the first time, a new instance of ZCL_VENOR is created and stored in GT_MULTITON; and that very instance is returned.

When ZCL_VENDOR=>GET_INSTANCE( ‘12345’ ) is called again, the existing instance of ZCL_VENDOR in GT_MULTITON is returned instead of a new instance. That saves memory and runtime.

For more information on multiton, you can refer to my book Design Patterns in ABAP Objects.

Now, what is the value-add of this post?

Instead of creating a specialized multiton implementation into every required class, I have created a general purpose multiton class which does all the hard work of caching objects. All you have to do is to implement an interface into your existing class to add multiton functionality.

Let’s assume that our vanilla class looks like this.

CLASS zcl_bc_multiton_demo DEFINITION
  PUBLIC
  FINAL
  CREATE PUBLIC .

  PUBLIC SECTION.

    data:
      gv_id    type char15,
      gv_erdat type erdat,
      gv_ernam type ernam.

    methods:
      constructor importing iv_id type char15.

  PROTECTED SECTION.
  PRIVATE SECTION.
ENDCLASS.

CLASS zcl_bc_multiton_demo IMPLEMENTATION.

  method constructor.
    gv_id = iv_id.
    gv_erdat = sy-datum.
    gv_Ernam = sy-uname.
  endmethod.

ENDCLASS.

Pretty simple, huh? This is the class we presumably need multiton functionality on.

This is the interface we need to implement.

interface ZIF_BC_MULTITON
  public .

  class-methods:
      get_instance
        importing
          !iv_objectid type CDOBJECTV
        returning
          value(ro_obj) type ref to ZIF_BC_MULTITON
        raising
          CX_SY_CREATE_OBJECT_ERROR.

endinterface.

After implementing the interface, our vanilla class looks like this.

CLASS zcl_bc_multiton_demo DEFINITION
  PUBLIC
  FINAL
  CREATE PUBLIC .

  PUBLIC SECTION.

    interfaces ZIF_BC_MULTITON.

    data:
      gv_id    type char15,
      gv_erdat type erdat,
      gv_ernam type ernam.

    methods:
      constructor importing iv_id type char15.

  PROTECTED SECTION.
  PRIVATE SECTION.
ENDCLASS.



CLASS zcl_bc_multiton_demo IMPLEMENTATION.

  method constructor.
    gv_id = iv_id.
    gv_erdat = sy-datum.
    gv_Ernam = sy-uname.
  endmethod.

  METHOD zif_bc_multiton~get_instance.
    ro_obj ?= new zcl_Bc_multiton_demo( conv #( iv_objectid ) ).
  ENDMETHOD.

ENDCLASS.

And here is the general purpose class that does the caching.

CLASS zcl_bc_multiton DEFINITION
  PUBLIC
  FINAL
  CREATE public .

  PUBLIC SECTION.

    class-METHODS:
      get_obj
        IMPORTING
          !iv_clsname   TYPE seoclsname
          !iv_objectid  TYPE cdobjectv
        RETURNING
          VALUE(ro_obj) TYPE REF TO zif_bc_multiton
        RAISING
          cx_sy_create_object_error.

  PROTECTED SECTION.
  PRIVATE SECTION.

    TYPES:
      BEGIN OF t_multiton,
        clsname  TYPE seoclsname,
        objectid TYPE cdobjectv,
        cx       TYPE REF TO cx_sy_create_object_error,
        obj      TYPE REF TO zif_bc_multiton,
      END OF t_multiton,

      tt_multiton
        TYPE HASHED TABLE OF t_multiton
        WITH UNIQUE KEY primary_key COMPONENTS clsname objectid.

    class-DATA:
      gt_multiton TYPE tt_multiton.

ENDCLASS.



CLASS zcl_bc_multiton IMPLEMENTATION.

  METHOD get_obj.

    ASSIGN gt_multiton[
        KEY primary_key COMPONENTS
        clsname  = iv_clsname
        objectid = iv_objectid
      ] TO FIELD-SYMBOL(<ls_mt>).

    IF sy-subrc NE 0.

      DATA(ls_mt) = VALUE t_multiton(
        clsname  = iv_clsname
        objectid = iv_objectid
      ).

      TRY.

          CALL METHOD (ls_mt-clsname)=>zif_bc_multiton~get_instance
            EXPORTING
              iv_objectid = ls_mt-objectid
            RECEIVING
              ro_obj      = ls_mt-obj.

        CATCH cx_sy_create_object_error INTO ls_mt-cx ##no_handler.
        CATCH cx_root INTO DATA(lo_diaper).

          ls_mt-cx = NEW #(
            textid    = cx_sy_create_object_error=>cx_sy_create_object_error
            classname = CONV #( ls_mt-clsname )
            previous  = lo_diaper
          ).

      ENDTRY.

      INSERT ls_mt
        INTO TABLE gt_multiton
        ASSIGNING <ls_mt>.

    ENDIF.

    IF <ls_mt>-cx IS NOT INITIAL.
      RAISE EXCEPTION <ls_mt>-cx.
    ENDIF.

    ro_obj = <ls_mt>-obj.

  ENDMETHOD.

ENDCLASS.

Now, if we need to create an instance of ZCL_BC_MULTITON_DEMO bypassing the multiton cache, all we need to do is to create the object regularly as demonstrated below.

DATA(lo_obj) = NEW zcl_bc_multiton_demo( 'DUMMY' ).

If we need to get advantage of multiton, here is what we need to do.

DATA(lo_obj) = CAST zcl_bc_multiton_demo(
  zcl_bc_multiton=>get_obj(
    iv_clsname = 'ZCL_BC_MULTITON_DEMO'
    iv_objectid = 'DUMMY'
  )
).

 

Pretty neat, eh? Having ZCL_BC_MULTITON_DEMO, we don’t need to deal with caching anywhere else. This class will do the multiton caching for you, and return the cached instance in case you re-call GET_OBJ with the same object id.

In case you need the original source codes of the samples mentioned here, you can visit my GitHub ABAP library.

Standard

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s