Suppose I have a requirement to calculate the total area of a series of shape, for example circle and rectangle.
So I have two classes, circle and rectangle, both have a method get_area to return their area:

class ZCL_CIRCLE definition
  public
  final
  create public .

public section.

  methods CONSTRUCTOR
    importing
      !IV_RADIUS type FLOAT .
  methods GET_AREA
    returning
      value(RV_RESULT) type FLOAT .
protected section.
private section.

  data RADIUS type FLOAT .
ENDCLASS.

CLASS ZCL_CIRCLE IMPLEMENTATION.
  method CONSTRUCTOR.
    radius = iv_radius.
  endmethod.

  method GET_AREA.
    CONSTANTS: pai TYPE float value '3.14'.
    rv_result = pai * radius * radius.
  endmethod.
ENDCLASS.
class ZCL_RECTANGLE definition
  public
  final
  create public .

public section.

  methods CONSTRUCTOR
    importing
      !IV_HEIGHT type FLOAT
      !IV_WIDTH type FLOAT .
  methods GET_AREA
    returning
      value(RV_RESULT) type FLOAT .
protected section.
private section.

  data HEIGHT type FLOAT .
  data WIDTH type FLOAT .
ENDCLASS.



CLASS ZCL_RECTANGLE IMPLEMENTATION.
  method CONSTRUCTOR.
    height = iv_height.
    width = iv_width.
  endmethod.

  method GET_AREA.
    rv_result = width * height.
  endmethod.
ENDCLASS.

Original implementation

REPORT ZCALCULATE1.

TYPES: BEGIN OF ty_shape,
          shape TYPE REF TO object,
       END OF ty_shape.

TYPES: tt_shape TYPE STANDARD TABLE OF ty_shape.

DATA: lt_shape TYPE tt_shape,
      lv_result TYPE float.
START-OF-SELECTION.
  data(lo_circle) = new zcl_circle( 1 ).
  data(entry) = value ty_shape( shape = lo_circle ).
  APPEND entry TO lt_shape.

  data(lo_circle2) = new zcl_circle( 1 ).
  entry = value #( shape = lo_circle2 ).
  APPEND entry TO lt_shape.

  data(lo_rectangle) = new zcl_rectangle( iv_width = 1 iv_height = 2 ).
  entry = value #( shape = lo_rectangle ).
  APPEND entry TO lt_shape.

  LOOP AT lt_shape ASSIGNING FIELD-SYMBOL(<shape>).
     IF <shape>-shape IS INSTANCE OF zcl_circle.
        lv_result = lv_result + cast zcl_circle( <shape>-shape )->get_area( ).
     ELSEIF <shape>-shape IS INSTANCE OF zcl_rectangle.
        lv_result = lv_result + cast zcl_rectangle( <shape>-shape )->get_area( ).
     ELSE.
        "reserved for other shape type in the future
     ENDIF.
  ENDLOOP.

  WRITE: / lv_result.

The disadvantage of this solution

As each entry in internal table lt_shape points to a generic reference “REF TO OBJECT”, in the runtime we have to use keyword INSTANCE OF to check out what exactly the concrete shape this reference is pointing to. After the type is determined, we have to use keyword CAST to get a converted object reference so that the dedicated get_area method could be called.
This solution violates the OO principle “Open for extension and close for modification”. As seen in line 35, suppose in the future we have to support more shape types for example triangle, we have to add another IF branch to deal with triangle, which means to fulfill new requirement we have to change existing code.

Solution using CL_OBJECT_COLLECTION

1. since both circle and rectangle are a kind of shape, I declare an interface here:

interface ZIF_SHAPE
  public .
  methods GET_AREA
    returning
      value(RV_RESULT) type FLOAT .
endinterface.
Now both ZCL_CIRCLE and ZCL_RECTANGLE implement this interface:

2. The consumer report now is improved as below:

REPORT ZCALCULATE2.
  data: lv_result TYPE float.
  data(lo_container) = new cl_object_collection( ).
  data(lo_circle) = new zcl_circle( 1 ).
  lo_container->add( lo_circle ).

  data(lo_circle2) = new zcl_circle( 1 ).
  lo_container->add( lo_circle2 ).

  data(lo_rectangle) = new zcl_rectangle( iv_width = 1 iv_height = 2 ).
  lo_container->add( lo_rectangle ).

  data(lo_iterator) = lo_container->get_iterator( ).

  WHILE lo_iterator->has_next( ).
    data(lo_shape) = cast ZIF_SHAPE( lo_iterator->get_next( ) ).
    lv_result = lv_result + lo_shape->get_area( ).
  ENDWHILE.

  WRITE: / lv_result.
We can easily observe that this new OO style solution is much more simple, cleaner and flexible.

Thanks to Polymorphism, we succeed to avoid the damned IS INSTANCE OF check in IF ELSE branch. now for the variable lo_shape, its static reference, in design time, points to the abstract interface ZIF_SHAPE. In the runtime, the dynamic reference points to the dedicated shape instance, circle or rectangle, so that the GET_AREA implemented in each interface implementation class can be called accordingly.
With this approach, no matter what new shape type we have to support in the future, we DO NOT need to touch existence code, but just create new class which implements the ZIF_SHAPE interface and implements the very logic in GET_AREA method and that’s all. The principle “Open for extension and close for modification” is then achieved.
To report this post you need to login first.

11 Comments

You must be Logged on to comment or reply to a post.

  1. Dzmitry Baliuk

    Hello Jerry,

    Thanks for info about iterator in ABAP. But in this case we can write a code without it, can`t we?

    try.
    
    loop at lt_shape assigning field-symbol(<shape>).
      lv_result = lv_result + cast zif_shape( <shape>-shape )->get_area( ).
    endloop.
    
    catch cx_move_cast_error.
    endtry.

    Kind regards,
    Dmitry

    (4) 
    1. Sandra Rossi

      I agree with Dmitry, the demonstration of the iterator here does not show an interest compared to internal tables. For more complex iterations (tree parsing, etc.), I think we may use iXML (maybe CL_OBJECT_COLLECTION, but I don’t know this class). The blog post “just” demonstrates the polymorphism.

      Your example corresponds to this program (I didn’t test it, but it should compile/work wth 0 or few corrections):

      REPORT ZCALCULATE3.
      DATA: lv_result TYPE float.
      DATA: lt_shape TYPE TABLE OF REF TO zif_shape,
        data(lo_circle) = new zcl_circle( 1 ).
        APPEND lo_circle TO lt_shape.
      
        data(lo_circle2) = new zcl_circle( 1 ).
        APPEND lo_circle2 TO lt_shape.
      
        data(lo_rectangle) = new zcl_rectangle( iv_width = 1 iv_height = 2 ).
        APPEND lo_rectangle TO lt_shape.
      
        LOOP AT lt_shape ASSIGNING FIELD-SYMBOL(<shape>).
          lv_result = lv_result + <shape>->get_area( ).
        ENDLOOP.
      
        WRITE: / lv_result.
      
      (1) 
  2. Jerry Wang Post author

    Hello Dmitry, Sandra,

    Thanks a lot for your attention and time on this blog, really appreciate! 🙂 Yes, I agree with you that for this simple example, the usage of iterator does not show its importance.

    In the original implementation of this blog, we just use internal table lt_shape as one kind of storage approach. Then the worker ( area calculator ) just loops at this internal table to get result. From design point of view, the worker knows too much – it knows that the shape instances are stored in an internal table which in fact is not NECESSARY for it to know at all. All it should to do is just using a generic way to TRAVERSE the container – this generic operation should be done with a new abstract layer – iterator.

    In ABAP we only have internal table as built-in container, consider in Java we have built-in container like Vector, ArraryList, LinkedList, Hashmap provided by JDK. Using iterator solution, we can totally encapsulate those detail to consumer. Suppose in the beginning the container uses ArrayList to store shape instances, and for the first solution, it uses the FOR LOOP to calculate area sum. Later new requirement comes in, and the container has to switch to HashMap instead. Now, the consumer code will be broken, since you could never perform FOR LOOP on HashMap. And using the iterator solution, the consumer code can always work no matter how container storage logic changes.

    The essential drawback of the first solution is, it generates a tough dependency between the container internal storage strategy and the consumer code. Such dependency is eliminated by introducing an abstract layer with help of iterator.

    I fully agree that this new abstract layer will lead to some overhead, however I think this overhead is worth in some complex project. 🙂

    Best regards,
    Jerry

    (1) 
    1. Sandra Rossi

      Thanks a lot Jerry for the clarification, till I think OO theory and practical implementation of a language are different things, and there’s still no ABAP official iterator (this class being part of the WebDynpro ABAP Workbench), and probably one of many in the ABAP landscape; maybe it will disappear in a next release). The only official iterator is still officially the “internal table”, unfortunately.

      (0) 
      1. Jacques Nomssi

        Hello Sandra,

        an iterator over an internal table is cheap. Look at my comments here.

        In other news, I propose to display the class and sequence diagram because it can be automated. I have just done that for a variant of ZCALCULATE2:
        Class

        Sequence:

        For me, this a case of evaluating coupling. We divide our code in modules, but do we know if this additional complexity brings benefits? With the diagrams above, the discussion starts easily.

        JNN

        (1) 
  3. Tobias Schnur

    Hi Guys,

    in an OO environment using polymorphism it feels much more natural to me to use the iterator approach. I mean that’s a concept broadly used in every object oriented programming lanuage.

    Regards

    Tobias

    (0) 
  4. Naimesh Patel

    Thanks for the iterator design pattern. I wrote about it a while back ABAP Objects Design Patterns – Iterator

    I did implement my own collection handler instead of using the CL_OBJECT_COLLECTION. Mainly due to these reasons:

    • Do you know why ADD, REMOVE and CLEAR are not part of the IF_OBJECT_COLLECTION? It makes more sense to have them as part of the interface itself.
    • GET_ITERATOR only returns the object ref to CL_OBJECT_COLLECTION. It should return IF_OBJECT_COLLECTION for lose coupling.

    Thanks.

    (2) 
    1. Jerry Wang Post author

      Hello Naimesh,

      I checked your series of ABAP Object design patterns – they are well written and organized! Thanks a lot for providing the link into this blog, so that more SCNers can read and learn ABAP OO design pattern.

      Best regards,
      Jerry

      (0) 
  5. Matthew Billingham

    There’s been some complaints that comments to this blog aren’t getting published. For those who’ve complained, I’ve checked, and can’t see anything obvious. If you have been affected, please post a question with the tag using SAP.com

    (0) 
  6. Paul Hardy

    I think I am missing something obvious here.

    I understand that in ABAP we  have the good old internal table, and not the various different arrays to be found in other languages, like Java, arrays like vectors and hash browns an the like.This was, above, deemed to be “unfortunate” so there must be something we are missing out on using internal tables only? Can anyone tell me what that is.

    Secondly, if the example above does not show any benefit of using an iterator as opposed to looping over an internal table except for “that’s the way we do it in other languages” maybe another example would make things clearer? I see that in Java or something you don’t know if your data is in a Hash Brown or an Egg McMuffin, so you abstract the difference away. But in ABAP we only have the internal table, woe is us,

    I am now going to press the “submit comment” and chances are the huge gaps I have placed between my paragraphs will vanish.

    (0) 

Leave a Reply