Thursday, 1 June 2017

The Prototype Design Pattern At Work

In this blog, see how the prototype design pattern is used for item cloning in an ABAP system.

Item Clone

In this example, we will work on an imaginary purchase requisition application where the user has to enter the vendor code (LIFNR), material number (MATNR), unit price (PREIS and WAERS), and quantity (MENGE and MEINS) into the GUI. We will want it to be possible to enter multiple line items.
Below demonstrates what the model class of such an application would roughly look like.


CLASS zcl_purchase DEFINITION

  PUBLIC FINAL CREATE PUBLIC.


  PUBLIC SECTION.

    “ Some type definitions


  METHODS add_line IMPORTING !is_line TYPE t_line.

  METHODS load_from_db IMPORTING !iv_docno TYPE zdocno.

  METHODS save_to_db.


  PRIVATE SECTION.

    “ Some hidden variables

    “ Some boring methods

  PROTECTED SECTION.

ENDCLASS.


CLASS zcl_purchase IMPLEMENTATION.


  METHOD add_line.

    APPEND VALUE #(

      obj_material = NEW zcl_material( is_line-matnr )

      lifnr = is_line-lifnr

      matnr = is_line-matnr

      price = VALUE #(

        preis = is_line-preis

        waers = is_line-waers )

      quan = VALUE #(

      menge = is_line-menge

      meins = is_line-meins )

    ) TO gt_line.

  ENDMETHOD.


  METHOD load_from_db.

    “ Some boring code containing SELECT, etc.

  ENDMETHOD.


  METHOD save_to_db.

    “ Some boring code containing MODIFY, etc.

  ENDMETHOD.


ENDCLASS.

So far, so good. As you see, GT_LINE is a nested internal table with five fields: one object (OBJ_MATERIAL); two variables (LIFNR, MATNR); and two work areas (PRICE, QUAN). At this point, we will assume that creating a new CL_MATERIAL has a high runtime cost, which means that CL_MATERIAL~CONSTRUCTOR contains costly code snippets that take quite some time to execute.

Below demonstrates what the CL_MATERIAL class looks like.

CLASS zcl_material DEFINITION

  PUBLIC FINAL CREATE PUBLIC.


  PUBLIC SECTION.

    “ Some type definitions
 

    METHODS constructor IMPORTING !iv_matnr TYPE matnr.

      “ Some further methods


  PRIVATE SECTION.

    “ Some hidden methods

  PROTECTED SECTION.

ENDCLASS.


CLASS zcl_material IMPLEMENTATION.


  METHOD constructor.

    “ Some very slow but unavoidable code

  ENDMETHOD.


  “ Some more methods

ENDCLASS.

If the user enters a new line item containing a brand new material, we must call ADD_LINE again. Thus, because we will be creating a new instance of CL_MATERIAL we will have to endure high runtime cost. On the other hand, what if the user enters a material number that he/she had entered before? Would we still create a brand new instance of CL_MATERIAL? With our current code, we would. So we must ask ourselves, “Is there a better way?”

Luckily there is! What we will do is clone an existing material object instead of creating a new one—the core logic of the prototype design pattern. Let’s look at the Unified Modeling Language (UML) diagram below to see what this should look like.

SAP ABAP Guide, SAP ABAP Tutorials

The UML diagram has only one extra method: CLONE. This method will do exactly what it says: clone the object. That’s the easy part; the relatively tricky part will transpire in CL_PURCHASE. Let’s start simple and see what CL_MATERIAL looks like below.

CLASS zcl_material DEFINITION

  PUBLIC FINAL CREATE PUBLIC.


  PUBLIC SECTION.

    “ Some type definitions


    METHODS constructor IMPORTING !iv_matnr TYPE matnr.

    METHODS clone RETURNING VALUE(ro_material) TYPE REF TO zcl_material.

    “ Some further methods


  PRIVATE SECTION.

    “ Some hidden methods

  PROTECTED SECTION.

ENDCLASS.


CLASS zcl_material IMPLEMENTATION.


  METHOD constructor.

    “ Some very slow but unavoidable code

  ENDMETHOD.


  METHOD clone.

    SYSTEM-CALL OBJMGR CLONE me TO ro_material.

  ENDMETHOD.


  “ Some more methods

ENDCLASS.

Normally, within the CLONE method, we would create the object RO_MATERIAL (avoiding the slow code in the constructor using flag variables and conditions) and copy the entire state of ME to RO_MATERIAL. However, we used a cheat code. What you see is a system call to clone ME, which is not meant for external use. No one gets hurt by little hacks like this, as long as the unit tests are done well.

Now, back to CL_PURCHASE. Instead of creating a new CL_MATERIAL object every time ADD_LINE is called, we will clone an existing material object whenever possible. The code to do so can be seen below.

METHOD add_line.

    DATA lo_material TYPE REF TO zcl_material.

    ASSIGN gt_line[ matnr = is_line-matnr ]

      TO FIELD-SYMBOL(<ls_line>).

    IF sy-subrc EQ 0.

      lo_material = <ls_line>-obj_material->clone( ).

    ELSE.

      lo_material = NEW zcl_material( is_line-matnr ).

  ENDIF.



  APPEND VALUE #(

    obj_material = lo_material

    lifnr = is_line-lifnr

    matnr = is_line-matnr

    price = VALUE #(

      preis = is_line-preis

      waers = is_line-waers )

    quan = VALUE #(

      menge = is_line-menge

      meins = is_line-meins )

    ) TO gt_line.

ENDMETHOD.

Brilliant, isn’t it? Instead of re-calling the constructor every time the same material is added, we simply cloned an existing object and avoided the initialization runtime cost.

No comments:

Post a Comment