Additional Blogs by Members
cancel
Showing results for 
Search instead for 
Did you mean: 
Former Member
0 Kudos

Often you have the situation that an algorithm does not depend on the specific type of the data it processes, but only on certain common features all data types in question must expose. One way to express this circumstance is to use OO polymorphism through mechanisms like interfaces or class hierarchies, but in some cases this approach seems too cumbersome, especially if the objects you're concerned with really are simple data structures. With its generic and dynamic types, data references, field symbols and related stuff, ABAP provides some powerful means to deal with these situations, however the resulting code is often a little awkward, and in some cases you can bring them to bear only with great difficulty. Recently I was faced with such a situation and the solution I came up with depended on the use of includes, allowing me to program against data type placeholders, with the actual type definitions supplied later by the including context. Probably this is a well known idiom in the ABAP language, but since I had to figure it out for myself, it may also be news to some of you.

A simple example

To illustrate the approach, let's consider the following simplified example: Suppose our application's database layer wraps all database tables (i.e. ZGN_T001, ZGN_T002, ...) in function groups and allows write access only through update modules. For ZGN_T001 the interface of the update module ZGN_T001_UPDATE is given by:


*"---------------------------------------------------------------------- *"*"Update Function Module: *" *"*"Local Interface: *" TABLES *" TAB_INSERT STRUCTURE ZGN_T001 *" TAB_UPDATE STRUCTURE ZGN_T001 *" TAB_DELETE STRUCTURE ZGN_T001 *"----------------------------------------------------------------------

Furthermore, let's assume our application records database changes via change documents and in order to do bulk updates compiles a list of changes using the structure VZG_T001 generated by transaction SCDO:


types begin of VZGN_T001. include type ZGN_T001. include type ICDIND. types end of VZGN_T001.

Here the value in indicator field KZ of structure ICDIND determines whether the data row is to be Inserted, Updated or Deleted from the database. Hence, in order to call the update module with the data being available in this form, there's a simple transformation needed to switch between these two equivalent representations of the same information. We want to decouple our application from the implementation details of the database access layer and therefore define the following interface:


interface ZIF_GN_UPDT_MODULE_ADAPTER public . methods UPDATE importing !I_TAB_DATA type STANDARD TABLE. endinterface.

It's now the responsibility of the classes implementing this interface to take care of the necessary transformation step and call the update modules, so any possible changes in the way the database is accessed can be handled by them and won't affect the upper layers of the application.

Our model so far consists of the following components:



For each update module in the database layer we end up with a dedicated adapter class. Each of these classes needs to implement method ZIF_GN_UPDT_MODULE_ADAPTER~UPDATE, which is shown below for class ZCL_GN_UM_T001_ADAPTER:


method ZIF_GN_UPDT_MODULE_ADAPTER~UPDATE. data L_TAB_INSERT type ZGN_TTY_T001. data L_TAB_UPDATE type ZGN_TTY_T001. data L_TAB_DELETE type ZGN_TTY_T001. data L_STR_CD_DATA type VZGN_T001. data L_STR_DATA type ZGN_T001. loop at I_TAB_DATA into L_STR_CD_DATA. move-corresponding L_STR_CD_DATA to L_STR_DATA. case L_STR_CD_DATA-KZ. when 'I'. append L_STR_DATA to L_TAB_INSERT. when 'U'. append L_STR_DATA to L_TAB_UPDATE. when 'D'. append L_STR_DATA to L_TAB_DELETE. endcase. endloop. call function 'ZGN_T001_UPDATE' tables TAB_INSERT = L_TAB_INSERT TAB_UPDATE = L_TAB_UPDATE TAB_DELETE = L_TAB_DELETE. endmethod.

Clearly, the above algorithm does not depend on the specifics of ZGN_T001, indeed the implementation for class ZCL_GN_UM_T002_ADAPTER looks identical, except for referencing type ZGN_T002 and its friends, of course. Unfortunately, it's not straightforward to get rid of this redundancy (e. g. by somehow pushing the common parts up to ZCL_GN_UPDT_MODULE_ADAPTER) and thus adhere to the DRY principle.

There are probably a number of ways around this dilemma, I want to propose the following one:

We create two includes ZGN_IC_UPDT_MODULE_ADAPTER_DEF and ZGN_IC_UPDT_MODULE_ADAPTER_IMP, in which we define respectively implement a local class LCL_UPDT_MODULE_ADPAPTER that implements interface ZIF_GN_UPDT_MODULE_ADAPTER. The transformation algorithm contained in this class exclusively refers to the types ITY_STR_DATA and ITY_STR_CD_DATA which (and that's the crux) we refuse to define within our includes. Instead, we depend on the object including our class to supply the necessary definitions. In effect, we thus create something akin to a template class with type parameters.

Now, to take advantage of this class, we need to marry it to the adapter classes we outlined previously. We use a scheme where the global adapter classes forward invocations of their update method to a private instance of LCL_UPDT_MODULE_ADPAPTER they create in their constructor. In turn, these instances invoke their container's CALL_UPDT_MODULE method, which each adapter class redefines to call the respective update module.

This is the model we end up with:



Let's have a look at the corresponding code. First we consider the aforementioned includes ZGN_IC_UPDT_MODULE_ADAPTER_DEF:


*&---------------------------------------------------------------------* *& Include ZGN_IC_UPDT_MODULE_ADAPTER_DEF *&---------------------------------------------------------------------* class LCL_UPDT_MODULE_ADAPTER definition. public section. interfaces ZIF_GN_UPDT_MODULE_ADAPTER. methods CONSTRUCTOR importing I_RCL_CONTAINER type ref to ZCL_GN_UPDT_MODULE_ADAPTER. protected section. types LTY_STR_DATA type ITY_STR_DATA. types LTY_STR_CD_DATA type ITY_STR_CD_DATA. types LTY_TAB_DATA type standard table of LTY_STR_DATA with default key. data M_RCL_CONTAINER type ref to ZCL_GN_UPDT_MODULE_ADAPTER. endclass.

and ZGN_IC_UPDT_MODULE_ADAPTER_IMP:


*&---------------------------------------------------------------------* *& Include ZGN_IC_UPDT_MODULE_ADAPTER_IMP *&---------------------------------------------------------------------* class LCL_UPDT_MODULE_ADAPTER implementation. method CONSTRUCTOR. M_RCL_CONTAINER = I_RCL_CONTAINER. endmethod. method ZIF_GN_UPDT_MODULE_ADAPTER~UPDATE. data L_TAB_INSERT type LTY_TAB_DATA. data L_TAB_UPDATE type LTY_TAB_DATA. data L_TAB_DELETE type LTY_TAB_DATA. data L_STR_CD_DATA type LTY_STR_CD_DATA. data L_STR_DATA type LTY_STR_DATA. loop at I_TAB_DATA into L_STR_CD_DATA. move-corresponding L_STR_CD_DATA to L_STR_DATA. case L_STR_CD_DATA-KZ. when 'I'. append L_STR_DATA to L_TAB_INSERT. when 'U'. append L_STR_DATA to L_TAB_UPDATE. when 'D'. append L_STR_DATA to L_TAB_DELETE. endcase. endloop. M_RCL_CONTAINER->CALL_UPDT_MODULE( I_TAB_INSERT = L_TAB_INSERT I_TAB_UPDATE = L_TAB_UPDATE I_TAB_DELETE = L_TAB_DELETE ). endmethod. endclass.

By introducing the type parameters ITY_STR_DATA and ITY_STR_CD_DATA we managed to avoid any reference to the actual types ZGN_T001, ZGN_T002, ... and their dependants. The includes are then embedded into the global adapter classes' sections provided for local class definitions/implementations. For class ZCL_GN_UM_T001_ADAPTER this amounts to:


*"* use this source file for any type declarations (class *"* definitions, interfaces or data types) you need for method *"* implementation or private method's signature types ITY_STR_DATA type ZGN_T001. types ITY_STR_CD_DATA type VZGN_T001. include ZGN_IC_UPDT_MODULE_ADAPTER_DEF.

and


*"* local class implementation for public class *"* use this source file for the implementation part of *"* local helper classes include ZGN_IC_UPDT_MODULE_ADAPTER_IMP.

(For some reason since WAS 6.40 referencing includes here leads to a warning in the syntax check. Bother.) The last thing we need to do is create an instance of the included local class in the constructor of the adapter class:


method CONSTRUCTOR . SUPER->CONSTRUCTOR( ). create object M_RIF_ADAPTER type LCL_UPDT_MODULE_ADAPTER exporting I_RCL_CONTAINER = ME. endmethod.

As reward for our efforts we managed to factor out the generic parts of the transformation step into a separate class and thereby reduced the amount of code repetition. Of course, realizing our idea turned out to be a little involved and for the simple example outlined here the net gain is probably not worth the trouble. However, the idiom can easily be applied in more complicated situations and then saves a lot of time fiddling around with dynamically created types, field symbols and you name it.

4 Comments