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.
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.
No comments:
Post a Comment