Application Development Blog Posts
Learn and share on deeper, cross technology development topics such as integration and connectivity, automation, cloud extensibility, developing at scale, and security.
cancel
Showing results for 
Search instead for 
Did you mean: 
ennowulff
Active Contributor
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... 😞
28 Comments
matt
Active Contributor
I'd also like to see a real SAP world example.
hardyp180
Active Contributor

Five years ago what I was trying to do was go through all the Java examples in “Head First Design Patterns” and re-write them in ABAP.

They had an example of the decorator pattern trying to build up the price of a cup of coffee. In my hotel example I was just playing around trying to combine disparate types of obects e.g. swimming pool, hotel bar etc.. based on a hotel I visited in Moscow which had a car showroom inside it.

Back in reality I encountered a situation where I could not use filter BADIs for some reason (I cannot remember why).

The problem was that during a business process certain logic should only be applied for certain countries, and certain logic for certain product lines, and it kept getting more and more complicated.

I did not want to have a subclass explosion, so the inhertitance tree was only one level deep (usually). Some subclasses dealt with country specific logic, some with product line secific logic, and some not yet created subclasses will deal with whatever crazy logic is thrown at me.

My factory class gets a structure based into it using “context” data which is just country and product line at the moment, but might (almost 99% sure it will) get extra fields added in the future.

I have a comment in the “returned decorated object” method of the factory as follows:-


*——————————————————————–*
* Get Sub Classes
*——————————————————————–*
* The list of subclasses comes out such that children of children
* come at the end.
* This is fine for the decorator pattern. The base class is at the
* centre, in the same way that a Christmas tree is at the centre of
* all the decorations that get added to it
* Put another way, the base class is added first, and then wrapped
* with any relevant subclasses
*——————————————————————–*

and then a bit later on…

*——————————————————————–*
* Using the decorator pattern, if RMC and BE are passed in, we want…
* The base class on the inside
* Then any RMC specific subclasses
* Then any BE  specific subclasses
* Then the RMC/BE subclass on the outside
*——————————————————————–*

RMC stands for ready mixed concrete, by the way, and BE is Belgium.

The model class has what I call “exit” methods and they call the equivalent method in the predecssor and then do their own thing.

As Enno alluded to it is horrendously complicated, but it works like a charm. The calling program passes in the context data to the factory and gets back an object and the caller does not know or care if that object instance has been “decorated” or not.

I can also create a new subclass without changing either the calling program or the factory.

As I said the filter BADI concept works in pretty much the exact same way, you get an arbitray number of classes with the same interface,all doing their own thing, I just wish I could remember why I could not use it in this case.

Cheersy Cheers

Paul

 

 

pokrakam
Active Contributor

I’m with you on this one, have never seen the point of the decorator. To me it looks like ‘sideways subclassing’, neither composition nor inheritance, but fares badly at trying to be both because to me the complexity negates the benefit.

I understand the concept, but have always managed with composition-based solutions such as tables of interfaces. Example:

interface lif_option. "implemented by classes option_metallic, option_automatic etc.
...
data(car) = new lcl_car( ).
data(options) = lcl_option_factory=>get_options( user_selections ).
car->configure( options ).
car->get_price( ).

Note, greatly simplified and contrived to match your example. Perhaps I’d use a builder class to handle merging of options with car but this is just a quick example.

Basically I'm applying 'composition over inheritance' to the decorator pattern 🙂

nomssi
Active Contributor
Hello Enno,

my 2 cents: patterns are concept, not rote. The decorator intent is to attach additional responsibilities to existing code at run-time. I think this is what is done in legacy ABAP when a list of function modules (internal table) with the same interface called dynamically. In ABAP OO I would probably use the BAdI framework, e.g. BAdI with multiple implementations and a default option.

The good thing is, you can implement this logic from scratch  (I once did this for a mapping tool). I think we are not discussing this more often because the original developer often does not need this flexibility. It is added complexity that is only (extremely) helpful for the maintenance guys.

Here a picture generated from your code:



JNN
former_member183045
Contributor

One example where I like to use the decorator (or maybe a slighty adapted version of it)  is for logging or tracing – hopefully that counts for a real world example in SAP.
The key point for me is the interface which helps to switch fast and on exactly one point between the raw functionality and the extended functionality – in the example below between logging and not logging.

Another example which comes into my mind is to calculate the weight of  packaged products.

The class for the product will probably know the weight of itself. If the weight for the product wrapped with the packaging material for shipping is needed an separeted decorator class “product_with_package” has the advantage that no adaption on the product class is needed.

 

interface ZIF_CAR_BUILDER
public .
methods:
build_car.
endinterface.

CLASS ZCL_CAR_BUILDER DEFINITION
PUBLIC
FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES ZIF_CAR_BUILDER.
ALIASES build_car FOR zif_car_builder~build_car.
PROTECTED SECTION.
PRIVATE SECTION.

ENDCLASS.

CLASS ZCL_CAR_BUILDER DEFINITION IMPLEMENTATION.

METHOD build_car .
" do whatever you have to do for building a car
ENDMETHOD.
END CLASS.


CLASS zcl_car_builder_decorated_with_trace DEFINITION
PUBLIC
FINAL
CREATE PUBLIC .
PUBLIC SECTION.

INTERFACES ZIF_CAR_BUILDER.
ALIASES: build_car FOR zif_car_builder~build_car.
METHODS: constructor importing i_car_builder type ref to zif_car_builder.

PROTECTED SECTION.
PRIVATE SECTION.
DATA:
car_builder TYPE REF TO ZIF_CAR_BUILDER,
ENDCLASS.



CLASS zcl_car_builder_decorated_with_trace IMPLEMENTATION.

METHOD constructor.
car_builder = i_car_builder.
ENDMETHOD.

METHOD build_car.
" do some logging, tracing or whatever else
car_builder ->build_car ().
" do some logging tracing or whatever else
endmethod.
endclass.


""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" sample client execution with the advantage that you can switch between logging and not logging easily
" and have only one if for logging functionality and not on each logging call

DATA car_builder type ref to zif_car_builder().

DATA(real_car_builder) = new zcl_car_builder();

if logging_enabled.
car_builder = new zcl_car_builder_decorated_with_trace( real_car_builder )
else.
car_builder = real_car_builder.
endif.


.

 

*) Image changed

former_member557751
Participant
Hi Enno,

interesting. I use the decorator pattern very often. I think it is an elegant way to extent functionality without changing existing code. One of the best articles about the decorator pattern I found is this one (don't get confused by the title; great site about software design, by the way).

https://blog.ploeh.dk/2010/04/07/DependencyInjectionisLooseCoupling/

The decorator pattern also combines with other patterns quite well. Here is an example of decorator pattern combined with command pattern.

https://github.com/treisinger/TR_Abap_Examples/blob/master/y_command_processor_pattern.prog.abap

Tapio
former_member557751
Participant

Hi Jacques,

I think BAdI is not really the best way to construct a decorator. Most of the time you need an order, e.g. you need to to do security, validation and logging. You can create an order with BAdI BADI_SORTER, but I found this quite cumbersome.

Best regards, Tapio

matt
Active Contributor
I skimmed your post at first reading and wondered why a Russian Hotel would offer ready mixed concrete to guests.
matt
Active Contributor
0 Kudos
According to this article https://dzone.com/articles/is-inheritance-dead the decorator is applied through composition.
matt
Active Contributor
If the original programmer does not need the complexity of decorator, then he/she shouldn't put it in - in accordance with the least work principle of doing exactly what is needed. A later programmer wanting to add functionality will then refactor the original code so that it does use the decorator pattern (if that's appropriate!).

 
matt
Active Contributor
A few years ago, I wrote a file handling class. Initially it was used only to read CSV files from the presentation server, but later, through refactoring and subclasses, I adjusted it to read xlsx files, the clipboard, and the appserver.

It seems to me now, that I could achieve this better by using the decorator.

Just off the top of my head, I need decorators for

  • Appserver source

  • Presentation server source

  • Clipboard source

  • CSV format

  • xlsx format


In the opinion of the group - would this be the right approach? Is it workable?

 
pokrakam
Active Contributor

Yes, he starts off with a pure composite decorator, then describes a drawback that is resolved by a ‘workaround’ which involves inheriting from an abstract decorator class, at which point he’s essentially is back to the ‘classical’ decorator pattern that uses inheritance.

Emperor’s new clothes I say ?

pokrakam
Active Contributor

Off the top of my head, my approach would be a file meta-class and source (stream?) and interpreter interfaces implemented by a bunch of concrete classes.

INTERFACE if_stream.  "Has implementations App Server, Clipboard, etc.
METHODS open IMPORTING source TYPE string
RETURNING result TYPE REF TO if_stream.
METHODS read RETURNING result TYPE string.

INTERFACE if_interpreter. "has implementations CSV, EXCEL, etc.
METHODS derive_table IMPORTING stream TYPE REF TO if_stream
RETURNING VALUE(result) TYPE t_my_table.

INTERFACE cl_stream_factory.
METHODS get_stream IMPORTING stream_type TYPE t_stream_types
RETURNING VALUE(result) TYPE REF TO if_stream.

INTERFACE cl_interpreter_factory.
METHODS get_interpreter IMPORTING format TYPE t_format
RETURNING VALUE(result) TYPE REF TO if_stream.
...

CLASS cl_file_handler DEFINITION.
METHODS get_table IMPORTING i_source_type TYPE if_interpreter=>t_stream_type
i_format TYPE if_interpreter=>t_format
i_source TYPE string
RETURNING VALUE(result) TYPE t_my_table..
...

METHOD get_table.

data(stream) = cl_stream_factory=>get_stream( i_source_type ).
stream->open( i_source ).

data(interpreter) = cl_interpreter_factory=>get_interpreter( i_format ).
data(result) = interpreter->derive_table( i_stream ).

This is obviously just a quick idea. With a real implementation it would include a lot more error handling, abstract factories, less chaining; and throughout implementation would be refactored into something completely different ?

Still, the basic idea is a workable start for something that contains neither decorator nor inheritance.

matt
Active Contributor
0 Kudos
Only one inheritance though to address a specific drawback as the pattern is implemented in Java (And would be in ABAP). Not really classic inheritance.
pokrakam
Active Contributor
Are we not just moving the inheritance from the functional class to the pattern implementer and making the caller do more work? To me the hierarchy and all the passthrough methods are still unnecessary complexity.

Perhaps because ABAP is so heavily data- and table- oriented, it is an ABAP-specific thing that this kind of processing is easier to implement in linear fashion than the semi-recursion that the decorator resembles.

I've just never made friends with it. To me it is too complex for the (ABAP?) problems it tries to solve and I have always come up with a simpler way in the scenarios I've encountered.
former_member610590
Participant
If you want that for client there were only one class you could use that approach.

 

In that case I would use factory method for every type of file or Chain of Responsibility pattern.
matt
Active Contributor
The starting point would be something like

 
gimme_an_object( i_type = c_csv i_source = c_presentation_server ).
matt
Active Contributor
So far, I've  not used it. But I don't think it violates the composition over inheritance rule. Even though, for just one level only, it uses inheritance.
pokrakam
Active Contributor

I wouldn’t put it quite as harsh as ‘violating the rule’, inheritance has it’s time and place.

But that aside, yes we’ve removed inheritance from the main class, but if we change or add a method to the class, we potentially still need to change all the decorators. To me this is brittle and still retains some of the drawbacks of inheritance. An author of a class may not be aware of all the decorators that may rely on a signature because they are now two levels of where-used-ness away.

A facade or tabular composition construct can be used to a similar effect and is simpler and more robust IMHO. Like Enno, I simply haven’t come across a need in ABAP, but am open to change my mind if someone can present a good use case.

nomssi
Active Contributor
It is workable, but it would only be the right approach if the various implementation can mix (do they? e.g. xslx from the clipboard?). If not, it is overkill: state/strategy comes to mind.

Decorator is a mix of objectify and object recursion, so we expect to execute more than one concrete decorator implementation at run-time. This is why the standard example is pricing, where we execute all price conditions routines to get the final price. We do not need to re-invent this wheel in ABAP.
ennowulff
Active Contributor
Thanks, Andreas, for this yes-I-would-say-this-is-a-real-world-example-example!

But... 😉

As the logger does not have a "real" connection to the "car", it can only log "car has been built". There is no "insight-logging" possible, what I would prefer because I want to know about what's happening when building the car...?!

Cheers

Enno
ennowulff
Active Contributor
0 Kudos
Thanks for your reply, Paul! I understood that you wrapped some specified classes (country and product line) to a base class. The base class provides some functionality; the "subclasses" provide some extra functionality. But how dow you get to this extra functionality? The caller does not know about the decorations. So the added functionality must have an impact to the base class. but how?

Cheers

Enno
ennowulff
Active Contributor
0 Kudos
if that’s appropriate!

I would assume, it's mostly not. Refactoring a could to using a decorator is definitely not a think I would do or suggest any other to do. Maybe because I am not really aware of how the decorator works.

Plus: I think this would break any existing unit test.

But: your intention of course is right: Do what is required and not more.
ennowulff
Active Contributor
0 Kudos
Hey Tapio,

I am not surprised by your statement that you use the decorator quite often, as you are one of the really oo experts I know... 😉

Thanks for your example. Adding caching mechanism to a class sound really real-world and promising! Here I can understand how the base and the decorator are related, because the cache-decorator does not need to know anything about the class (or instances) it's caching and vice versa.

I wil need some minutes to understand your command decorator example. Thanks for this example!

Meanwhile I think the thing I struggle with most is the idea of what could be the real additional functionality.

 

Cheers
~Enno
ennowulff
Active Contributor

Thanks to all for your comments and interest in this topic!

I think when I really understand this pattern, I moved one level up in oo thinking… ?

Next thing I will concentrate on is “caching with the decorator” which now comes very close to my understanding of the decorator and promises great advantages for data access objects. Normally the caching is provided within the class what makes the class more complex and you will have to test not only the class itselft but also the caching mechanism. If there should be a way of having a universal decorator for this then this would be great.

Keep decorating your life and code

.´*•.¸(*•.¸♥¸.•*´)¸.•*´
♥«´¨`•°..ENNO..°•´¨`»♥
.¸.•*(¸.•*´♥`*•.¸)`*•.

 

ennowulff
Active Contributor
0 Kudos
That's one of the often mentioned examples for a decorator. But the decorator pattern seems to be oversized for me. I see no benefit to a specified class that simply uses the base object as "input" to get the data from it.

 
Domi
Contributor
For me this discussion exactly shows the main points about patterns:

  • don't implement any pattern just because you can or someone told you so!

  • often your implementation - or new/changed requirements - leads you to a pattern

  • there is always a different way

  • patterns are suggestions - use them if suitable but adjust them to your needs - Code is Law 😉

  • follow the idea behind patterns - not the pattern(name): e.g. strategy and decorator has nearly equal implementations; I didn't know mutliton before 'Design Patterns in ABAP Objects', I called it key-singleton;...

  • you can use words factory, singleton, decorator, state, composite, facade,... and everyone has a similar picture in mind - no explanation needed

  • ...


For me patterns are a structured and accepted way for "dynamic programming in ABAP"!

I love dynamic solutions with interfaces, "Class/Method Naming Conventions", activating functionality via customizing,... even with RTTI ;), and some of my colleagues really hated my for this!

But most of these implementations follow an "official design pattern", and therefor I just had to explain these patterns once and tons of coding was understandable!

 

And to provide a real-world-example: I need to fill some custom IDoc segments of CRMXIF_PARTNER_SAVE01.

I changed this coding today in the morning - guess the pattern 😉

before:
  METHOD if_ex_crmxif_partner_map~change_mapped_data_out.

LOOP AT cs_data_mapped ASSIGNING FIELD-SYMBOL(<ls_partners>).

set_common_fields( CHANGING c_partner_data = <ls_partners> ). "<= added 2 month ago

set_cng_fields( CHANGING c_partner_data = <ls_partners> ). "<= initial implementaion

set_dipo_fields( EXPORTING is_recipient = is_recipient "<= initial implementaion
CHANGING c_partner_data = <ls_partners> ).

ENDLOOP.

ENDMETHOD.

 

1 interface and  3 classes later:
  METHOD if_ex_crmxif_partner_map~change_mapped_data_out.

DATA additional_mapper TYPE REF TO y0if_cpi_xifmapper.
DATA(additional_mapper_classes) = VALUE string_table( ( |ZCL_CPI_XIFMAPPER_COMMON| )
( |ZCL_CPI_XIFMAPPER_CNG| )
( |ZCL_CPI_XIFMAPPER_DIPO| ) ).

LOOP AT cs_data_mapped ASSIGNING FIELD-SYMBOL(<ls_partners>).

LOOP AT additional_mapper_classes ASSIGNING FIELD-SYMBOL(<additional_mapper_classe>).

TRY.
CREATE OBJECT additional_mapper TYPE (<additional_mapper_classe>).
CATCH cx_sy_create_object_error.
DELETE additional_mapper_classes WHERE table_line = <additional_mapper_classe>.
CONTINUE.
ENDTRY.

additional_mapper->change_mapped_data( EXPORTING is_recipient = is_recipient
CHANGING c_partner_data = <ls_partners> ).

ENDLOOP.

ENDLOOP.

ENDMETHOD.

 

I don't feel adding a new class to the ITab will violate the OpenClosePrinciple, so for me this is a simple way to allow enhancements but with some control and documented (the main BADI method is recored in a transport request!)

 

regards

Domi
ennowulff
Active Contributor
0 Kudos
Hey Domi,

thanks for your additions and suggestions!
follow the idea behind patterns – not the pattern(name):

That's exactly what my colleage tells me each time I ask him about design patterns... 😉  And this is exactly the main difficulty IMHO. When learning Design Patterns you must go the way of learning the names and their behaviour. After that you can decide by "behaviour" knowing that there is a specific name for it...

~Enno
Labels in this area