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: 
keremkoseoglu
Contributor
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.

What is multiton, anyway?

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. (source: Design Patterns in ABAP Objects)

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.

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.
12 Comments