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
4,480
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
nomssi
Active Contributor
Hello Kerem,

I get your point here but as always, the developer has to balance complexity (new class + interface implementation) with flexibility.

I made my caching code a little simpler with the DEFAULT idiom:
result = VALUE #( table_expr DEFAULT general_expr ).

If you are like me and not like Horst Keller, you have been frustrated by the reader/writer positions, functional operand positions, numeric expression position... but this is a case of general expression position so instead of writing code with IF we can separate Query from Modifier by extracting a new_instance( ) method
 CLASS-METHODS new_instance IMPORTING iv_lifnr      TYPE lifnr
RETURNING VALUE(ro_obj) TYPE REF TO zcl_vendor.

METHOD new_instance.
ro_obj = NEW #( iv_lifnr ).
INSERT VALUE #( lifnr = iv_lifnr
obj = ro_obj ) INTO TABLE gt_multiton.
ENDMETHOD.

and simplify get_instance( )
  METHOD get_instance.
ro_obj = VALUE #( gt_multiton[ KEY primary_key COMPONENTS lifnr = iv_lifnr ]-obj
DEFAULT new_instance( iv_lifnr ) ).
ENDMETHOD.

With this, I did not feel I needed a central caching/registry yet, but maybe in the future.

 

best regards,

JNN
former_member201527
Participant
0 Kudos
good one kerem.koseoglu.

 

Regards,

Nikhil Kulkarni
mmcisme1
Active Contributor
Very nice!!!!!   So nice of you to share.  I can see where it will help me out.  I love the way you wrote the blog too.   It "talked" to me.

<Sigh> This is another one I'll bookmark.

Michelle
iftah_peretz
Active Contributor
Classy (pun intended)!!!
RAF
Active Contributor
Hi,

very nice one.

But please keep in mind that as long as your table holds the reference the ABAP garbage collection can not remove the objetcs. So unused objects can blow up the memory consumption, which is contradicting your purpose.

 

BR
SuhaSaha
Advisor
Advisor
0 Kudos
Hello jacques.nomssi

What do you think of defining the multiton "cache" as PUBLIC READ-ONLY?

BR,

Suhas
nomssi
Active Contributor
Hello Suhas,

as always, the developer has to balance complexity with flexibility, so it will depend on your use case (I hope you did not expect any other answer).

I can image using a such a pattern for singleton, multiton (now that I learned from Kerem), memoization, cache proxy. I will assume you want to provide a special access point.

Let me try to evaluate the strengths and limits of the pattern:

Making the cache public read-only has the merit to provide a single global access point to all client with the best performance possible. As it cannot be changed outside the class, it mitigate one problem with global variables. It is often the simplest thing that could possibly work and you will know how to change it later.

On the other hand, the cache is now part of the public interface of your object. Exposing attributes that are not absolutely needed will yield code that cannot be redirected or extended/decorated like a method call. This makes e.g. unit testing more difficult (no test SEAM). Changes are likely to break existing clients.

JNN
bruno_esperanca
Contributor
0 Kudos
Hi Jacques,

Good to see you!

Do you really use a static method to create instances normally, or was this just for demonstration purposes?

Cheers,
Bruno
nomssi
Active Contributor
Hello Bruno,

nice to read from you!

In this case the pattern requires a single access to the "cached" objects so the static method is needed. But yes, I generally I like using factory method. Nowadays I name those new( ) instead factory( ).

JNN
waldemar_schakiel
Participant
0 Kudos
Hi Kerem,

your approach with a common factory multiton class is very nice. It is particularly suitable for entities with a one key field.

What is your strategy for entities with multi fileld keys? Are you trying to convert the multi field key into a string field?

Maybe you have developed another suitable method? Let me know what is your prefered approach for such cases.

Greetings

Waldemar
keremkoseoglu
Contributor
0 Kudos
A string combination would work, yes. Preferably separated by semicolon or brackets.
joachimrees1
Active Contributor
0 Kudos
Awesome, thanks for sharing!
Labels in this area