Skip to Content

Design Patterns in ABAP – Visitor (Part 1)

All is Not Quiet on New Years Day

The coming year – 2017 – is going to be incredibly busy (as per normal) but also a lot more interesting than usual. There are 3 big things on my plate at work. At long last I am actually getting to use Gateway in a productive application, but the crazy science fiction thing is that my company has embraced artificial intelligence and machine learning and I am involved doing some ABAP programming to facilitate this. The last item is that I also have to write a program to upload some spreadsheets into SAP.

I also hope to go to SAP TECHED in Las Vegas in October, and no doubt I will be speaking for at least one SAP conference here in Australia, in the recently re-opened exhibition centre in Darling Harbour. I’ll be doing some book reviews of SAP Press works as well, starting with the one on ODATA I am currently reading (and actually using in real life).

So, no doubt there will be a lot to write about, blog wise, in 2017. However, before I get going on that there is something I planned to write about on the SCN three years ago, but never got around to, due to suddenly having to start writing books as opposed to just reading other peoples.

Pattern Cake, Pattern Cake, Bakers Man

At that point (2013) I had just finished the “Head First Design Patterns” book and having read the SAP equivalent by Igor Barbaric was merrily translating the Java code in the HFDP book into ABAP and seeing if I could get an actual benefit in my day to day work.

As an example, here is something I wrote back then about the “Decorator” pattern.

https://blogs.sap.com/2013/03/02/oo-design-patterns-decorator-in-abap/

I tend to take some example code from a book or article I find interesting, translate it into ABAP and then start poking and prodding it, and changing bits and pieces until it becomes totally unrecognisable from the original.

I found a really good chapter of a book written by Robert Martin (Uncle Bob) back in 2002 all about the “Visitor” pattern. I intended to subject this to the same exercise i.e. translate it from Java and then turn it inside out, but never got around to it. Now is the time to rectify this. At time of writing a new SAP Press book has come out all about design patterns in ABAP and looking at the contents that talks about the Visitor as well. I have not read that book yet, and I will not even think about doing so until I finish this blog, to avoid any risk of plagiarism.

Of course I might come up with exactly the same example by random chance, but luckily not many other people use Monsters in their examples, so I think I am fairly safe.

Is it real, or just a dream?

I did read the free chapter from the new ABAP Design Patterns book though, I could not stop myself. Luckily it did not mention the Visitor. What I did find there is a problem I have written about myself many times.

The problem is thus – it is all very good saying something (like OO programming) is wonderful but no-one is going to believe you unless you give them a concrete example that they can instantly relate to, and use the very next time they come across the problem this wonderful new thing (whatever it might be) is designed to solve.

A lot of writers use the ever popular “it is good because I say so” technique with no further detail, and then wonder why the message is not getting across. I think in some ways SAP are guilty of this heinous crime in their explanation of OO programming in general, which is why, 16 years after “ABAP Objects” was invented a huge swathe of people are still firmly in procedural world. It does not help when the ABAP 7.40 certification exam seems to be encouraging you to use “classic” exceptions in your methods as opposed to exception classes.

The next problem is people using examples which are not very relevant/realistic. Amazing as it may seem, I have been accused of this myself! Some people have said I should use objects like sales orders in my examples, as opposed to hundred foot tall fire breathing monsters. They say the former more accurately represents the day to day experience of over 90% of actual SAP programmers.

We could go backwards and forwards all day arguing the point as to whether it is actually 80% or 90% of programmers, but regardless I can understand the view that some programmers go through their entire career and never do any Monster related programming at all. I feel quite sorry for them.

In the same way, in the free chapter of the new ABAP Design Patterns book the author talks about a certain pattern and says the only way he can possibly explain it is to use the pattern to solve a problem which no actual ABAP programmer will ever have to deal with. This is somewhat akin to saying “Look at this! It’s really good, it’s got no practical application, but it’s REALLY GOOD”. If you have to struggle and struggle to come up with a real life example, and fail, then this falls into the “solution looking for a problem, and not finding one” basket.

BRF Plus Ca Change

Likewise you may say that if the only examples I think of for my wonderful solutions are Monster related then I have a similar disconnect with the real world. There is a subtle difference though, and I will attempt to explain where my examples actually come from.

If you read the BRFPlus chapter of my book, you would see the example business logic of the various options available to the customer for customising their Monster e.g. how many elephants it can balance on one finger. After reading the rules you might be forgiven for thinking I made those rules up after being hit on the head with a baseball bat, directly after drinking a bottle of vodka down in one.

It’s worse than that. Often when you encounter something in real life that is truly bizarre you say “you can’t make these things up” and it is the same with those seemingly insane business rules. They were actual business requirements I was given on my last project and I wrote a gigantic business application in ABAP, using BRFPlus, to implement them. Obviously they were not actually about Monsters, they were about ready mixed concrete (one of the three core products my company makes) but all I had to do was a find and replace and all the concrete specific terminology with a monstrous equivalent. Moreover, it turns out the rules were not insane at all; they made perfect sense once the context was understood. They were always going to look crazy at first glance though.

Never Going to Fall For, Modem Love

Time to move on to the Visitor. Robert Martin’s example is all about modems and operating systems. Ten seconds ago I had a right go at someone for using an example to solve an operating system related problem, when ABAP is operating system agnostic. So I think I might to have to change the example. So once I gathered the problem the Visitor pattern was supposed to solve, I asked myself “In the last 19 years have you ever, even once, had a similar sort of problem where this would have helped?” If the answer had been NO DON’T BE STUPID then I would have given up there and then and this blog would never have been written.

As it transpires I did have such a problem, about seven years ago. I got the code working but I was not happy with my solution, not by a long shot. It seemed to be that it was literally impossible to solve my problem in an elegant manner given the limitations of ABAP. So let us see if the Visitor is in fact the miracle solution I was looking for.

As I just explained this is going to be a real business problem I actually had, but monsterised to protect the innocent.

International Monster Making Man of Mystery

The business empire of Baron Frankenstein is booming – he is expanding into literally dozens of new countries at once. As his senior ABAP programmer I have to ensure the SAP system can cope with this rapid series of changes. There is good news and bad news and more bad news and yet more bad news.

The good news is that we have worked out a way to interface SAP with the monster making machine (the one that needs to be hit by lightning to bring the monster to life). The bad news is that there are dozens of makes of monster making machines, we would love to standardise on just one model, but that is just not practical as no one model is available in every country.

The more bad news is that although the various companies that produce monster making machines have agreed a standard communication protocol called MLINK they have all implemented it a slightly different way, with different maximum field lengths and differing views on what fields are mandatory or prohibited.

The even more bad news is that each country has a big bunch of laws regarding monster making. In France for example monsters are not allowed to sell mortgages, but in the USA it is compulsory.

As a result the content of the data transmitted from SAP to the monster making machine via the interface, though always broadly similar, varies based on two factors – the make of machine, and the country where the monster is being created at dead of night during a thunderstorm. Moreover who is to say that at some point in the future a third dimension might be added, and the contents will again have to vary based on some factor the nature of which is currently unknown.

So we need a design which can cater easily for new makes of machine, new countries, and is future proof enough so that when that mysterious third factor is introduced we can introduce it with the minimum effort possible.

Figure 001 – Open Closed Principle

Now is the time for black magic, or rather the “Open Closed Principle”. This is where you change the behaviour of an existing program; make it do something new, without changing it in any way. This is because if you change a working piece of software it might break. If you have loaded it up with unit tests then it shouldn’t break – or if it does you will know instantly – but there is always going to be the risk.

The only way to be 100% sure something that works is still going to work is if you don’t change it. However we want the program to do something different now, like cater for a new country. So, it looks like we are asking for the impossible here, but in actual fact OO programming is literally crawling with ways to achieve this. The “decorator” I alluded to earlier is one such way; here we deal with the Visitor.

One important thing to note about design patterns is that they are patterns as opposed to actual solutions. So they are always going to be implemented differently – as an example I rarely see the most common design pattern (MVC) implemented the same way twice. People have huge disagreements about even the basics e.g. the data flow between the three components.

So my implementation of the Visitor is going to be somewhat different to Robert Martins, and I am going to put some ABAP specific stuff in, because if you have tools at hand, why not use them?

New Test Imminent

One thing I have been convinced of is the benefit of test driven development. So I start off by saying what success will look like – this is the so called “executable specification” as it is code that is going to look like a specification, and will actually compile and execute.

The test class is going to be written before the class it is testing even exists. Naturally it won’t compile, there will be a syntax error saying that the class being tested does not exist. That is the TDD “circle of life” – you deliberately write a test that will fail, and a syntax error is most definitely a “fail” and then do the minimum possible to get the test to pass e.g. create a blank class to stop the syntax error, and then “refactor” which is improving the code in some way if possible which in the initial step is actually putting some code in the blank class. This could be described as “first make it fail, then make it work, then make it good”.

The point here is that the test is utterly independent of the solution. The test describes the problem to be solved and tells you in no uncertain terms if that problem has been solved by whatever code the test is currently calling. In that “decorator” blog I mentioned at the start of this blog you will see me utterly blowing apart and totally redesigning the entire class structure and being able to leave the test code 99.9% unchanged.

In real life test classes can – and should be – gigantic, most likely 50%+ of the total code. That sends shivers down peoples (mainly managers) spine. The fact this makes your program rock solid stable forevermore is obscured by the fact it takes longer to write in the first place. This is why unit testing is far rarer than I would like in the ABAP world, the false economy of not bothering often seems to win.

To keep this simple I will just have two countries – the USA and France, and two makes of monster machine – ACME and BLOGGS – and just one business rule per country and make of machine. We are going to be mapping the monster related data in SAP to the format the monster making machine expects, subject to the variations imposed by the different countries and makes of machine.

Code to a Nightingale

Here we go with some real code. As I mentioned the names in the test class should give you a really good idea what it is that we are trying to achieve. If this is not obvious then the code has failed in its mission and should be shot.

Here comes the test class definition. At this stage it will not compile as it refers to all sorts of things that I have not yet created. You will note the blank interface which is needed so the test class can have access to anything within the class under test.

INTERFACE lif_unit_test.
* We need this so the class under test can be “friends” with the
* test class, by implementing this blank (marker) interface
ENDINTERFACE.

*——————————————————————–*
* Unit Tests
*——————————————————————–*
CLASS lcl_test_class DEFINITION FOR TESTING
RISK LEVEL HARMLESS
DURATION SHORT
FINAL.

PUBLIC SECTION.
INTERFACES lif_unit_test.

PRIVATE SECTION.
DATA: class_under_test TYPE REF TO lcl_monster_mapper,
country          TYPE t001w-land1,
machine_make     TYPE zde_monster_machine_make,
source_data      TYPE zst_monster_input_data,
target_data      TYPE zst_mlink_monster_input_data.

METHODS: setup,
“GIVEN…………………….
given_source_data,
given_country_is_france,
given_country_is_usa,
given_make_is_acme,
given_make_is_bloggs,
“WHEN……………………..
when_mapping_interface_data,
“THEN……………………..
then_target_data_is_correct,
“IT SHOULD…………………
interface_to_french_acme   FOR TESTING,
interface_to_french_bloggs FOR TESTING,
interface_to_usa_acme      FOR TESTING,
interface_to_usa_bloggs    FOR TESTING.

ENDCLASS.“Test Class Definition

The “given / when / then / it should” structure is something called “behaviour driven development” as created by Dan North, Dan, Dan, the Unit Testing Man, Dan Dare, Unit Tester of the Future, some people say that is different to test driven development, but I think they are one and the same.

I have a failing test class. What is the minimum I need to do to get this to pass (compile)? It is to create blank implementations for all the methods, and create the objects to which the data definitions refer. So I will do that right now.

Lost in France

Creating missing procedures in SE80 is no big deal; if you click on one that doesn’t exist the system will create one for you. This is not the case for methods, so the part where I create the skeleton methods and class under test used to be agony. Into this void steps ABAP in Eclipse.

There would be no point in using ABAP in Eclipse if it was not in some sense better than SE80 and here is the main advantage I can see. Naturally SAP could also build the ability to auto-create method skeletons into SE80, but then why would you want to use AIE? This is a very circular argument. As circular as Jock McCircular, the Circular Scotsman, winner of the all Scotland “going round in circles” contest 2016, who publishes a Circular about sailing in circles around Circular Quay.

In any event, can we please stop messing about and get back to the point at hand? That point is, if I fire up Eclipse, and then put my cursor anywhere in my test class definition and press CTRL + F1, the framework will create both the implementation for the last as a whole and each one of the methods for me. All in one go, I could not do that for twenty non-existent FORM routines in SE80.

That got rid of the vast bulk of syntax errors in one foul swoop, the only remaining one is the fact that the class under test LCL_MONSTER_MAPPER does not exist yet. Can we do a quick fix for that? Can we quick fix it? Yes we can. Can we quick fix it? Yes we can. Two seconds later a blank definition and implementation exists for this class as well. Then I press the “activate” button and everything compiles. So I have made it fail, then made it pass, now I had better fill in some of my test methods.

  • The SETUP method creates an instance of class under test (a new instance of the class for each test) and clears the global variables in the test class to avoid side effects, as the test methods are alleged to run in a random order.
  • The GIVEN methods fill in the test data to be used in the test e.g. “Given France” would set the country to France, or “Given ACME would set the machine make to ACME. The “Given Test Data” naturally sets up some baseline data which will be subtly altered during the mapping, based upon the variations in country and machine make.
  • Just as aside, SAP is working on the concept of “enumerations” which are common in other languages, so eventually (in a future release of ABAP) if, in my program, I try to pass in an invalid domain value to a variable that is based upon a data element based on that domain I will get an error.

I would be remiss if I did not say AIE takes some getting used to. The code completion is really good, but just different enough from SE80 to make you struggle at the start.

The actual test methods themselves take the following format:-

METHOD interface_to_french_acme.

given_source_data( ).

given_country_is_france( ).

given_make_is_acme( ).

when_mapping_interface_data( ).

then_target_data_is_correct( ).

ENDMETHOD.

Birdy Num Num

As another aside, this method of programming (TDD) was first invented by Spike Milligan, Peter Sellars and the rest of the Goons when they wrote the song “I’m Programming Backwards for Christmas”.

I am now going to make up some arbitrary business rules for the various countries and machine makes. In real life I had a list of such rules as long as your arm, which made me wonder if the various manufacturers of the devices I was interfacing to really understand the term “standardisation” when they all claimed they had agreed a standard interface. One of the ironies in life is that the UK and the USA cannot agree on how to spell the word “standardise”.

METHOD then_target_data_is_correct.

 

CASE country.

WHEN ‘FR’.

cl_abap_unit_assert=>assert_equals( act = target_data-mortgage_ability

exp = ‘1’ “Cannot Sell Mortgages

msg = ‘Incorrect Mortgage Ability’ ).

WHEN ‘US’.

cl_abap_unit_assert=>assert_equals( act = target_data-mortgage_ability

exp = ‘3’ “Must Sell Mortgages

msg = ‘Incorrect Mortgage Ability’ ).

ENDCASE.

 

CASE machine_make.

WHEN ‘ACME’.

cl_abap_unit_assert=>assert_equals( act = target_data-max_lightning_bolts

exp = 5

msg = ‘Incorrect Maximum Lightning Bolts’ ).

WHEN ‘BLOGGS’.

cl_abap_unit_assert=>assert_equals( act = target_data-max_lightning_bolts

exp = 10

msg = ‘Incorrect Maximum Lightning Bolts’ ).

ENDCASE.

 

ENDMETHOD.”Then Target Data is Correct

Almost done, I have set up the test data, I know what results I need, it follows I need to call some sort of mapping method in my “When” method.

METHOD when_mapping_interface_data.

 

target_data = class_under_test->do_mapping( source_data ).

 

ENDMETHOD.

Naturally the mapping method does not yet exist, as we are working backwards. I click on the name of the method that does not exist and the framework asks me if I want to create such a method. Moreover, it correctly guesses what parameters I want and their DDIC types.

Figure 002 – Creating a Method with signature

That is GREAT as Tony the Tiger would say before spraying you with cornflakes. The SE80 creation of FORM routines did not correctly give the right data type of your parameters for you.

I have now finished my test class and activate it. At this stage I have a test class which describes in detail what it is I am trying to achieve and it complies and can even run, though it won’t do a whole lot. The point is there is not one jot of productive code as yet.

With Single Power comes Single Responsibility

From now on, the only reason the test class will change is if there are some new business rules, or if the existing business rules change. The solution to the problem, a solution I have not yet written, can change as often as it likes utterly independently. The test class is responsible for stating what the problem is; the class being tested is responsible for solving that problem. Obviously if the problem changes the solution will most likely have to change, but not the other way around; there are many possible solutions to any given problem.

I’ve just had a thought. Many people tell me my blogs are too long and I should split them into two – or more – depending on just how long they go on for. It suddenly occurred to me it would be really funny to break off a blog with “Visitor” in the title before I have even gone anywhere near talking about the Visitor pattern, what it is, what it’s for, how to do this in ABAP and so on.

So that is just what I will do! I have split the finished blog into three and will publish one part each week.

To be continued….

To report this post you need to login first.

2 Comments

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

  1. Christopher Solomon

    LOVE IT!!! Awesome blog….great humor and nice walk through of rather “dry” subject matter (who loves testing?!?!?!). Funny enough, I do the same thing…ie “that’s cool…now how do I do this in ABAP?” and have been revisiting patterns/anti-patterns recently (reading through https://sourcemaking.com/  with my morning coffee)….so this is timely! Can’t wait for the next parts!

    p.s. As I got to the end I was thinking the same thing….”yes yes…you set up your “given…when…then”….but what about the Visitor pattern?!?!?!”…hahaha
     

    (0) 

Leave a Reply