There are design pattern. And there are design pattern. There are some I use really often because they are clear to me in use and technique, like the singleton or the factory pattern, the observer and some others. The decorator pattern for some reasons is very hard for me to understand. I learned about it in a workshop years ago. But over the years there was no need for me to use it and therefor no need to understand it. I try to change this for some weeks now...
I am not completely through with it...
The decorator pattern itself is described often and the UML diagram can mostly be found there. Up to now I have no idea what to use the pattern for in an ABAP environment. Nevertheless I tried to build up an example to understand how it works and what I maybe could do with it someday.
Characteristics Of A Decorator
The main principle of a decorator pattern is, to
decorate an existing object in a way that the object does not know about the decorations. But that is exactly my problem with this pattern: In which cases would I like to have something added to a class without knowing that there was something added? In most cases I had the decorations need to know at least something about the decorated object. That is mentioned as a disadvantage of this pattern: The decorations somehow need to inform the decorated object about what they have done. And this seems to me some kind of weird: using something that is especially decoupled and than building something to connect again.
How does the decorator work?
The decorator pattern creates a hierachical structure like the one you will get when using inheritance. The
predecessor object will be passed to the decorator object when being instantiated and stores it in a class attribute. This happens with each new decorator that will be applied.
After all decorations are done, for example the price can be queried by asking the last decoration about it. This decoration then asks the predecessor decorator about its price, add its own price and passes back the result.
When you think about ice cream the way for getting the price might be the following:
crumble->get_price
caramel_sauce->get_price
cream->get_price
vanilla->get_price
chocolate->get_price
cone->get_price
cone: 0,00
chocolate: ( 0,00 ) + 1,00
vanilla: ( 1,00 ) + 1,00
cream: ( 2,00 ) + 0,50
caramel_sauce: ( 2,50 ) + 1,00
crumble: ( 3,50 ) + 0,50
Result: 4,00
There is no connection between those components. That's good on one hand, because you simply can add new components and it will have no technical impact on the existing classes.
What are the disadvantages?
In my opinion the biggest disadvantage is, that the use case for this pattern is very limited. Another sensible question could be about the weight of the ice cream. That also can be answered very well equivalent to how to calculate the price. Querying a price and the weight looks quite reasonable, but what if I want to know more about the configuration? For example is there a component that has artificial sweeter? How many scoops of ice cream do I have? Do I need to get a bigger waffle cone?
If I think about SAP programming, there is no task where I would find the decorator pattern helpful. There is an example by
naimesh.patel on his
blog, but in my opinion its no decorator. The pattern itself is of course the decorator pattern, but the use case does not really fit. Why should I use a quite complex pattern like the decorator for producing different outputs?
Fictional Examples
There are many many examples for how to use the decorator. Most of them are like:
- making a coffee
- decorating a pizza
- selling ice cream (see above)
in order to have a total price afterwards.
bfeeb8ed7fa64a7d95efc21f74a8c135 already wrote 2013 about
decorating a hotel, but I didn't manage to reprogram his examples. additionally it is an example you will not find in an SAP system.
On his home page, Philipp Hauer also describes the
decorator pattern very detailed but again fiction examples... Very detailed information about the pattern can also be found on this page:
Configuring pizza.
Things I would always do with some kind of a structured table because creating classes for these options is not common. There was one example
Configuring A Car
Because of the lack of a better SAP-world example, I decided to use cars, because this is something most people do know about. Cars are quite common and everyone knows some special options cars have.So I decided to
decorate a car with some options. Having at least one well known thing in a world of new makes it easier to understand. Hopefully.
Classes
In my example program there is a
basic model class ("basic")and there are some
option classes:
- option_multimedia
- option_automatic
- option_metallic
- option_rallye
Of course there is the main decorator class "option" which is defined
abstract, because this class itself will not be used.
The class "basic" will be derived from "option". "Basic" is the main class for holding the basic car model.
The decorator class "option_decorator" will also be derived from the abstract "option" class but has the main feature of the decorator pattern: a private attribute for holding the predecessor option.
Class "option" (abstract)
"=== abstract option class providing needed methods
CLASS option DEFINITION ABSTRACT.
PUBLIC SECTION.
TYPES: BEGIN OF ts_option,
name TYPE string,
price TYPE i,
END OF ts_option,
tt_options TYPE STANDARD TABLE OF ts_option WITH DEFAULT KEY.
METHODS get_configuration ABSTRACT
RETURNING
VALUE(options) TYPE tt_options.
METHODS get_price ABSTRACT
RETURNING
VALUE(price) TYPE i.
DATA price TYPE i.
DATA name TYPE string.
ENDCLASS.
Class "basic"
"=== main class of "option" - this is the "basic model" which will be decorated
CLASS basic DEFINITION INHERITING FROM option.
PUBLIC SECTION.
METHODS get_configuration REDEFINITION.
METHODS get_price REDEFINITION.
METHODS constructor.
PROTECTED SECTION.
ENDCLASS.
CLASS basic IMPLEMENTATION.
METHOD get_configuration.
APPEND VALUE #( name = name price = price ) TO options.
ENDMETHOD.
METHOD constructor.
super->constructor( ).
price = 15000.
name = 'Basic Model '.
ENDMETHOD.
METHOD get_price.
price = me->price.
ENDMETHOD.
ENDCLASS.
Class "option_decorator"
"=== Option decorator - this class will handle the option pattern
CLASS option_decorator DEFINITION INHERITING FROM option.
PUBLIC SECTION.
METHODS constructor
IMPORTING
im_decorator TYPE REF TO option.
METHODS get_configuration REDEFINITION.
METHODS get_price REDEFINITION.
PRIVATE SECTION.
DATA lo_decorator TYPE REF TO option.
ENDCLASS.
CLASS option_decorator IMPLEMENTATION.
METHOD constructor.
super->constructor( ).
me->lo_decorator = im_decorator.
ENDMETHOD.
METHOD get_configuration.
CHECK lo_decorator IS BOUND.
options = lo_decorator->get_configuration( ).
ENDMETHOD.
METHOD get_price.
price = lo_decorator->get_price( ) + me->price.
ENDMETHOD.
ENDCLASS.
Classes "option_..."
"=== OPTION metallic paint
CLASS option_metallic DEFINITION INHERITING FROM option_decorator.
PUBLIC SECTION.
METHODS constructor
IMPORTING
im_decorator TYPE REF TO option.
METHODS get_configuration REDEFINITION.
METHODS get_price REDEFINITION.
ENDCLASS.
CLASS option_metallic IMPLEMENTATION.
METHOD constructor.
super->constructor( im_decorator ).
price = 500.
name = 'metallic paint'.
ENDMETHOD.
METHOD get_configuration.
options = super->get_configuration( ).
APPEND VALUE #( name = name price = price ) TO options.
ENDMETHOD.
METHOD get_price.
price = super->get_price( ).
ENDMETHOD.
ENDCLASS.
"=== OPTION multimedia system
CLASS option_multimedia DEFINITION INHERITING FROM option_decorator.
PUBLIC SECTION.
METHODS constructor
IMPORTING
im_decorator TYPE REF TO option.
METHODS get_configuration REDEFINITION.
METHODS get_price REDEFINITION.
ENDCLASS.
CLASS option_multimedia IMPLEMENTATION.
METHOD constructor.
super->constructor( im_decorator ).
price = 1000.
name = 'multimedia entertainment system'.
ENDMETHOD.
METHOD get_configuration.
options = super->get_configuration( ).
APPEND VALUE #( name = name price = price ) TO options.
ENDMETHOD.
METHOD get_price.
price = super->get_price( ).
ENDMETHOD.
ENDCLASS.
"=== OPTION automatic gear
CLASS option_automatic DEFINITION INHERITING FROM option_decorator.
PUBLIC SECTION.
METHODS constructor
IMPORTING
im_decorator TYPE REF TO option.
METHODS get_configuration REDEFINITION.
METHODS get_price REDEFINITION.
ENDCLASS.
CLASS option_automatic IMPLEMENTATION.
METHOD constructor.
super->constructor( im_decorator ).
price = 2000.
name = 'automatic gear'.
ENDMETHOD.
METHOD get_configuration.
options = super->get_configuration( ).
APPEND VALUE #( name = name price = price ) TO options.
ENDMETHOD.
METHOD get_price.
price = super->get_price( ).
ENDMETHOD.
ENDCLASS.
"=== OPTION rallye stripes
CLASS option_rallye DEFINITION INHERITING FROM option_decorator.
PUBLIC SECTION.
METHODS constructor
IMPORTING
im_decorator TYPE REF TO option.
METHODS get_configuration REDEFINITION.
METHODS get_price REDEFINITION.
ENDCLASS.
CLASS option_rallye IMPLEMENTATION.
METHOD constructor.
super->constructor( im_decorator ).
price = 100.
name = 'rallye stripes'.
ENDMETHOD.
METHOD get_configuration.
options = super->get_configuration( ).
APPEND VALUE #( name = name price = price ) TO options.
ENDMETHOD.
METHOD get_price.
price = super->get_price( ).
ENDMETHOD.
ENDCLASS.
Methods
There are two methods in the option class:
- get_price
- get_configuration
Get_price will ask the predecessor for its price and adds its own price. The options own price is defined in the CONSTRUCTOR.
The method get_configuration asks the predecessor for its configuration and appends it's own configuration (name) to the returning table.
Program
The report has four checkboxes respecting one of the four options. When beeing executed, the main object will be created: the "basic" class. For each selected option, the corresponding class will be created. The predecessor object (in case its the first option, the predecessor is the basic model), will be passed as importing parameter of the constructor.
START-OF-SELECTION.
"options selection
PARAMETERS p_mmdia AS CHECKBOX DEFAULT 'X'.
PARAMETERS p_autom AS CHECKBOX DEFAULT 'X'.
PARAMETERS p_metlc AS CHECKBOX DEFAULT 'X'.
PARAMETERS p_rally AS CHECKBOX DEFAULT 'X'.
DATA go_decorator TYPE REF TO option.
DATA go_predecessor TYPE REF TO option.
CREATE OBJECT go_decorator TYPE basic.
go_predecessor = go_decorator.
IF p_mmdia IS NOT INITIAL.
CREATE OBJECT go_decorator
TYPE option_multimedia
EXPORTING
im_decorator = go_predecessor.
go_predecessor = go_decorator.
ENDIF.
IF p_autom IS NOT INITIAL.
CREATE OBJECT go_decorator
TYPE option_automatic
EXPORTING
im_decorator = go_predecessor.
go_predecessor = go_decorator.
ENDIF.
IF p_metlc IS NOT INITIAL.
CREATE OBJECT go_decorator
TYPE option_metallic
EXPORTING
im_decorator = go_predecessor.
go_predecessor = go_decorator.
ENDIF.
IF p_rally IS NOT INITIAL.
CREATE OBJECT go_decorator
TYPE option_rallye
EXPORTING
im_decorator = go_predecessor.
go_predecessor = go_decorator.
ENDIF.
LOOP AT go_predecessor->get_configuration( ) INTO DATA(option).
WRITE: / option-name, 40 option-price.
ENDLOOP.
.
WRITE: / 'TOTAL', AT 40 go_predecessor->get_price( ).
Improvements
I made some improvements which I just want to mention but will not post due to too much complexity.
First upgrade: The decorator pattern itself has one quirk I really do not like: Creating an object with passing the current object and then overwriting the current object seems quite weird to me. So I tried to hide this in a helper class.
The second improvement is that in this version each option class has the same coding in get_price and get_configuration: Calling the previous object and adding own data to the result. I thought that this should be done in another way. So I derived another class "option_decorator_easy" from "option_decorator" to implement the two methods. All other option classes inherit from this new "easy" class. It makes the pattern even more complex but reduces code implementations.
Cancelling decoration
I also tried to cancel the decoration if a price limit has been reached. Maybe I didn't implement this the right way, but the way I did has the follwing issue: Each option just adds itself to the configuration. The configuration itself is called at the end, after all decorators have done their job. So I only had the chance to cancel the output of the options in get_price.
Conclusion
For me the decorator pattern is one of the most complicated pattern, because I find it hard to adapt this pattern to the SAP world. The pattern itself is quite complex. I don't want to think about how this looks like if there really is some functionality in the classes and if additionally interfaces are used...
In the SAP system there are some "decorator" classes. One for unit tests (CL_AUNIT_TEST_CLASS_DECORATOR). Even though I just keep me busy hacking the unit test world (
one,
two and
three) I do not understand how this decorator is used or what it can be used for.
Even if I could think about "decorating" a sales order (transport costs, dangerous goods issue, packing) I instantly see that the decorator is not the pattern that should be used. In most cases I want to interact with the options (if dangerous goods, the packing might be different, if transport by ship, the packing might be different, when packing the items I need to know about the size and so on).
If you have any SAP world examples for the decorator pattern I would be glad to hear!
I am happy for any suggestion, proposal or any other feedback about the post or the decorator and its use.
Cheers
~Enno
PS: sorry, no pictures...
😞