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.
Leave a Reply