Decorator pattern gives us flexibility to extend the behavior of certain objects at runtime. This can be seperate than the existing instances of the same class. To be able to achieve Decorator pattern, we need to create certain infrastructure for it.
Design time consideration:
- Create a Subclass of the main class, also called a component, as a Decorator class.
- In decorator class, create an attribute with
TYPE REF TO
main class - Create
CONSTRUCTOR
in the decorator class with importing parameter REF TO Decorator class. Set the attribute from this parameter. - Redefine all other methods in decorator class inherited from Main Class. Just call the same method using the reference of the main class.
- Create subclass of decorator when a new behaviour is required. Redefine the methods for which we need different behaviour.
UML
Let’s see the UML the example:
We have a main class OUTPUT
. We have base behavior as ALV output class ALVOUTPUT
. This behavior will execute all the time. Now, we’ll add the decorator to it as per the design time consideration steps.
Decorator class OPDECORATOR
with attribute O_DECORATOR
with TYPE REF TO OUTPUT
. In the CONSTRUCTOR
, we have the importing parameter IO_DECORATOR
. This will set up the link between the sub behavior. Method PROCESS_OUTPUT
is redefined to call the method from the O_DECORATOR
.
We have three different additional behavior – output in PDF, Email and Excel. This is being achieved by subclassing of the decorator and redefining the PROCESS_OUTPUT
method. Along with redefintion we’ll also call the SUPER->PROCESS_OUTPUT( )
to call the previous object’s method in the chain. Actual generation of PDF, Email and Excel is out of scope for this article.
Code Lines
Lets see the code:
REPORT znp_dp_decorator.
*&---------------------------------------------------------------------*
*& Purpose: Decorator Design Pattern Demo
*& Author : Naimesh Patel
*&---------------------------------------------------------------------*
* ===
CLASS output DEFINITION ABSTRACT.
PUBLIC SECTION.
METHODS:
process_output ABSTRACT.
ENDCLASS. "output DEFINITION
* ====
CLASS alvoutput DEFINITION INHERITING FROM output.
PUBLIC SECTION.
METHODS:
process_output REDEFINITION.
ENDCLASS. "alvoutput DEFINITION
*
CLASS alvoutput IMPLEMENTATION.
METHOD process_output.
WRITE: / 'Standard ALV output'.
ENDMETHOD. "process_output
ENDCLASS. "alvoutput IMPLEMENTATION
* ====
CLASS opdecorator DEFINITION INHERITING FROM output.
PUBLIC SECTION.
METHODS:
constructor
IMPORTING io_decorator TYPE REF TO output,
process_output REDEFINITION.
PRIVATE SECTION.
DATA: o_decorator TYPE REF TO output.
ENDCLASS. "opdecorator DEFINITION
*
CLASS opdecorator IMPLEMENTATION.
METHOD constructor.
super->constructor( ).
me->o_decorator = io_decorator.
ENDMETHOD. "constructor
METHOD process_output.
CHECK o_decorator IS BOUND.
o_decorator->process_output( ).
ENDMETHOD. "process_output
ENDCLASS. "opdecorator IMPLEMENTATION
* =====
CLASS op_pdf DEFINITION INHERITING FROM opdecorator.
PUBLIC SECTION.
METHODS: process_output REDEFINITION.
ENDCLASS. "op_pdf DEFINITION
*
CLASS op_pdf IMPLEMENTATION.
METHOD process_output.
super->process_output( ).
WRITE: /(10) space, 'Generating PDF'.
ENDMETHOD. "process_output
ENDCLASS. "op_pdf IMPLEMENTATION
* ======
CLASS op_xls DEFINITION INHERITING FROM opdecorator.
PUBLIC SECTION.
METHODS: process_output REDEFINITION.
ENDCLASS. "op_xls DEFINITION
*
CLASS op_xls IMPLEMENTATION.
METHOD process_output.
super->process_output( ).
WRITE: /(10) space, 'Generating Excel'.
ENDMETHOD. "process_output
ENDCLASS. "op_xls IMPLEMENTATION
* =====
CLASS op_email DEFINITION INHERITING FROM opdecorator.
PUBLIC SECTION.
METHODS: process_output REDEFINITION.
ENDCLASS. "op_email DEFINITION
*
CLASS op_email IMPLEMENTATION.
METHOD process_output.
super->process_output( ).
WRITE: /(10) space, 'Sending Email'.
ENDMETHOD. "process_output
ENDCLASS. "op_email IMPLEMENTATION
* ====
CLASS op_alv DEFINITION INHERITING FROM opdecorator.
PUBLIC SECTION.
METHODS: process_output REDEFINITION.
ENDCLASS. "op_alv DEFINITION
*
CLASS op_alv IMPLEMENTATION.
METHOD process_output.
super->process_output( ).
WRITE: /(10) space, 'Generating ALV'.
ENDMETHOD. "process_output
ENDCLASS. "op_alv IMPLEMENTATION
* ====
CLASS mainapp DEFINITION.
PUBLIC SECTION.
CLASS-METHODS:
run IMPORTING
iv_pdf TYPE flag
iv_email TYPE flag
iv_xls TYPE flag.
ENDCLASS. "mainapp DEFINITION
*
CLASS mainapp IMPLEMENTATION.
METHOD run.
DATA: lo_decorator TYPE REF TO output,
lo_pre TYPE REF TO output. " Helper Variable
* .... Setup objects
* standarad object
CREATE OBJECT lo_decorator TYPE alvoutput.
lo_pre = lo_decorator.
* testing Decorator
IF iv_pdf IS NOT INITIAL.
CREATE OBJECT lo_decorator
TYPE
op_pdf
EXPORTING
io_decorator = lo_pre.
lo_pre = lo_decorator.
ENDIF.
IF iv_email IS NOT INITIAL.
CREATE OBJECT lo_decorator
TYPE
op_email
EXPORTING
io_decorator = lo_pre.
lo_pre = lo_decorator.
ENDIF.
IF iv_xls IS NOT INITIAL.
CREATE OBJECT lo_decorator
TYPE
op_xls
EXPORTING
io_decorator = lo_pre.
lo_pre = lo_decorator.
ENDIF.
lo_decorator->process_output( ).
ENDMETHOD. "run
ENDCLASS. "mainapp IMPLEMENTATION
PARAMETERS: p_pdf AS CHECKBOX,
p_email AS CHECKBOX,
p_xls AS CHECKBOX.
START-OF-SELECTION.
mainapp=>run( iv_pdf = p_pdf
iv_email = p_email
iv_xls = p_xls
).
Run-time explanation
At runtime, we instantiate the object and we pass the previous object as reference. Thus we create the chain. Like when we instantiate the object for PDF, we pass the reference of the base behavior ALV. When we instantiate the object for Email, we’ll pass the object of PDF and so on and so forth. The chain of object would be something similar to this.
When we call the method process_ouput( ) of the LO_DECORATOR class as per the example, it would access each of the object in sequence and start processing from the last object in the chain and start processing in reverse as shown:
Hi Naimesh!
A heavy pattern, the decorator…!
Thanks for your example!! I’m trying to understand the decorator for a long time now. As there are no examples of how to use the decorator in a SAP context, I still did not understand it. Your example brought me a step further on my decorator way!! 😉
But I still must admit that I do not understand HOW this works…
I think in your example is a minor error: You do not use the OP_ALV-class although you defined it.
But what I am really missing: You are not really «decorating»! In fact you use the inheritance to produce different output. Of course you use the decorator technique, but in my opinion it’s no «decorating» because «decorating» means to put “additional things” to an object.
Therefore I tried to adapt your example and added costs for each output-variant.
In my example you will get an output like this:
Standard ALV output 1.000,00
Generating ALV 100,00
Generating PDF 10,00
Sending Email 50,00
===========
Total Cost: 1.160,00
The costs will be calculated the same way you produce the ouput: At the end with ONE call of CALCULATE_COSTS.
I think this describes the meaning of a decorator in a more comprehensible way?!
Again thanks for your example!
Btw: I bought the book “Head First Design Patterns” http://goo.gl/UIXgG
Awsome! a really good book for understanding design patterns! It’s not especially for ABAP but in most cases it can be simply adapted. Some examples are not very good, because they use design patterns (that means at least: classes) to get different prices. Things you normally yould realize with table entries…
Greetings from good old germany
Tricktresor-Enno
REPORT znp_dp_decorator.
*&---------------------------------------------------------------------*
*& Purpose : Decorator Design Pattern Demo
*& Author : Naimesh Patel
*& Co-Author: Enno Wulff (Adding the costs of each output)
*&---------------------------------------------------------------------*
* OUTPUT:
* Standard ALV output 1.000,00
* Generating ALV 100,00
* Generating PDF 10,00
* Sending Email 50,00
* ===========
* Total Cost: 1.160,00
*
*----------------------------------------------------------------------*
* ===
CLASS output DEFINITION ABSTRACT.
PUBLIC SECTION.
DATA deco_cost TYPE wertv8.
DATA cost TYPE wertv8.
METHODS:
process_output ABSTRACT,
calc_cost ABSTRACT RETURNING value(rv_cost) TYPE wertv8.
ENDCLASS. "output DEFINITION
* ====
CLASS alvoutput DEFINITION INHERITING FROM output.
PUBLIC SECTION.
METHODS:
process_output REDEFINITION
, calc_cost REDEFINITION.
.
ENDCLASS. "alvoutput DEFINITION
*
CLASS alvoutput IMPLEMENTATION.
METHOD process_output.
deco_cost = 1000.
WRITE: / 'Standard ALV output', 30 deco_cost.
cost = calc_cost( ).
ENDMETHOD. "process_output
METHOD calc_cost.
rv_cost = deco_cost.
ENDMETHOD. "calc_cost
ENDCLASS. "alvoutput IMPLEMENTATION
* ====
CLASS opdecorator DEFINITION INHERITING FROM output.
PUBLIC SECTION.
METHODS:
constructor
IMPORTING io_decorator TYPE REF TO output,
process_output REDEFINITION,
calc_cost REDEFINITION.
PROTECTED SECTION.
DATA: o_decorator TYPE REF TO output.
ENDCLASS. "opdecorator DEFINITION
*
CLASS opdecorator IMPLEMENTATION.
METHOD constructor.
super->constructor( ).
me->o_decorator = io_decorator.
ENDMETHOD. "constructor
METHOD process_output.
CHECK o_decorator IS BOUND.
o_decorator->process_output( ).
ENDMETHOD. "process_output
METHOD calc_cost.
rv_cost = o_decorator->cost + deco_cost.
ENDMETHOD. "calc_cost
ENDCLASS. "opdecorator IMPLEMENTATION
* =====
CLASS op_pdf DEFINITION INHERITING FROM opdecorator.
PUBLIC SECTION.
METHODS: process_output REDEFINITION.
ENDCLASS. "op_pdf DEFINITION
*
CLASS op_pdf IMPLEMENTATION.
METHOD process_output.
super->process_output( ).
deco_cost = 10.
WRITE: /(10) space, 'Generating PDF', 30 deco_cost.
cost = calc_cost( ).
ENDMETHOD. "process_output
ENDCLASS. "op_pdf IMPLEMENTATION
* ======
CLASS op_xls DEFINITION INHERITING FROM opdecorator.
PUBLIC SECTION.
METHODS: process_output REDEFINITION.
ENDCLASS. "op_xls DEFINITION
*
CLASS op_xls IMPLEMENTATION.
METHOD process_output.
super->process_output( ).
deco_cost = 20.
WRITE: /(10) space, 'Generating Excel', 30 deco_cost.
cost = calc_cost( ).
ENDMETHOD. "process_output
ENDCLASS. "op_xls IMPLEMENTATION
* =====
CLASS op_email DEFINITION INHERITING FROM opdecorator.
PUBLIC SECTION.
METHODS: process_output REDEFINITION.
ENDCLASS. "op_email DEFINITION
*
CLASS op_email IMPLEMENTATION.
METHOD process_output.
super->process_output( ).
deco_cost = 50.
WRITE: /(10) space, 'Sending Email', 30 deco_cost.
cost = calc_cost( ).
ENDMETHOD. "process_output
ENDCLASS. "op_email IMPLEMENTATION
* ====
CLASS op_alv DEFINITION INHERITING FROM opdecorator.
PUBLIC SECTION.
METHODS: process_output REDEFINITION.
ENDCLASS. "op_alv DEFINITION
*
CLASS op_alv IMPLEMENTATION.
METHOD process_output.
super->process_output( ).
deco_cost = 100.
WRITE: /(10) space, 'Generating ALV', 30 deco_cost.
cost = calc_cost( ).
ENDMETHOD. "process_output
ENDCLASS. "op_alv IMPLEMENTATION
* ====
CLASS mainapp DEFINITION.
PUBLIC SECTION.
CLASS-DATA l_cost TYPE wertv8.
CLASS-METHODS:
run IMPORTING
iv_pdf TYPE flag
iv_email TYPE flag
iv_xls TYPE flag.
ENDCLASS. "mainapp DEFINITION
*
CLASS mainapp IMPLEMENTATION.
METHOD run.
DATA: lo_decorator TYPE REF TO output,
lo_pre TYPE REF TO output. " Helper Variable
* .... Setup objects
* standard object
CREATE OBJECT lo_decorator TYPE alvoutput.
*==> Call Main output: ALV
lo_pre = lo_decorator.
CREATE OBJECT lo_decorator
TYPE
op_alv
EXPORTING
io_decorator = lo_pre.
IF iv_pdf IS NOT INITIAL.
*==> Decorating: PDF
lo_pre = lo_decorator.
CREATE OBJECT lo_decorator
TYPE
op_pdf
EXPORTING
io_decorator = lo_pre.
ENDIF.
IF iv_email IS NOT INITIAL.
*==> Decorating: PDF
lo_pre = lo_decorator.
CREATE OBJECT lo_decorator
TYPE
op_email
EXPORTING
io_decorator = lo_pre.
ENDIF.
IF iv_xls IS NOT INITIAL.
*==> Decorating: PDF
lo_pre = lo_decorator.
CREATE OBJECT lo_decorator
TYPE
op_xls
EXPORTING
io_decorator = lo_pre.
lo_pre = lo_decorator.
ENDIF.
*== Process output
lo_decorator->process_output( ).
*== Calculate costs
l_cost = lo_decorator->calc_cost( ).
write: /40 '==========='.
WRITE: / 'Total Cost:', 30 l_cost.
ENDMETHOD. "run
ENDCLASS. "mainapp IMPLEMENTATION
PARAMETERS: p_pdf AS CHECKBOX DEFAULT 'X',
p_email AS CHECKBOX DEFAULT 'X',
p_xls AS CHECKBOX.
START-OF-SELECTION.
mainapp=>run( iv_pdf = p_pdf
iv_email = p_email
iv_xls = p_xls
).
Hello Enno,
Nice to here from you!
Thanks for adding additional functionality to the example to make it more clear.
We already have a base functionality – ALV Output achieved by ALVOUTPUT class. We can add more functionality to the MAINAPP, without touching existing functionality. If you think from the MAINAPP perspective, we have achieved a decorator. Lets say you wish to create another Output of your data, fancy excel using XML. So, what you do is you inherit another class from the OPDECORATOR. In your MAINAPP, you add this new object in the chain to process it. You can add new functionality (decorating) your existing functionality.
More real business scenario would be any bolt on functionality to an existing program. If the designer had created decorator, it would be easier for the person who is going to modify it. As he needs to inherit a subclass, call the SUPER method in each method and finished.
If you are getting confused from enhancing the existing functionality of the object (e.g. ALV, or XLS or PDF), I guess it wont be Decorator. What do you think?
Regards,
Naimesh Patel