Skip to Content
Technical Articles

A Universal Design Pattern for ABAP OO Development and Unit Testing

Introduction

I would like to propose a new design pattern for organizing ABAP OO development and unit testing in a universal way.

This design pattern is based on interface and factory design pattern.

It has the following important features

  1. One factory class to create all interfaces’ instances.
  2. One injector class to inject the test double interface instance for unit testing.
  3. Determining the specific interface implementation class with design-time static configuration and run-time dynamic context.
  4. Using specific initialization data for a particular class implementing the same interface is possible.

What can it do

This design pattern contains two key components.

  1. The factory class to create interface instance dynamically.
  2. The injector class to assist unit testing.

Before diving into the theory parts, let’s have a visual view of the usage of this design pattern in programs and classes.

The usage of the factory class

There is a universal cross-client customizing table ZANDERS_IF_IMPL for design time class name configuration.

Here’s an example

The%20entry%20for%20table%20ZANDERS_IF_IMPL

One entry for table ZANDERS_IF_IMPL

There is a universal BADI ZANDERS_IF for run time class name determination.

BADI%20Definition%20and%20Implementation

BADI Definition and Implementation

When running the program ZANDERS_TEST_BUS1.

There are three sets of data to demo the functionality of the factory class ZAND_CF_OBJECT.

  1. KEY1:1, KEY2:1 for the default class name determination
  2. KEY1:1, KEY2:2 for the design time class name determination
  3. KEY1:1, KEY2:3 for the run time class name determination

Take the first set as an example.

When the debugging window shows up, we will see the class name for the variable lr_bus1 is ZANDERS_CL_BUS1.

Interface%20implementation%20class%20name

Interface implementation class name

The behavior for this program is that for the same interface ZANDERS_IF_BUS1, with different data sets there are three different classes can be used:

  1. ZANDERS_CL_BUS1
  2. ZANDERS_CL_BUS2
  3. ZANDERS_CL_BUS3

This feature gives amazing flexibility to customizing your coding logic based on the attribute of data.

The usage of the injector class

There is a class ZANDERS_CL_BUS1_ENH, which uses the interface ZANDERS_IF_BUS1.

In the method MY_DISPLAY, there is a local temporary usage of an interface instance.

An%20local%20temproray%20instance

A local temporary instance

If you have experience in unit testing, you will know the problem here.

For using test double, the local object has to be upgraded to a class-level attribute.

When implementing the unit testing, you have to go back to your real coding to do some changes.

Do you have such an unpleasant experience?

However, for this class, the strange thing is that when you perform unit testing, it passes successfully.

The secret is the usage of injector class.

This feature saves you the trouble of returning to modify the coding when creating unit testing.

The design thinking

Now it is time to see the theory part.

The following picture shows the architecture of this design pattern.

Design%20Overview

Design Overview

Let me explain the design by starting from the beginning point of a real coding process.

There is a requirement to display business data.

So an interface ZANDERS_IF_BUS1, which has only one method DISPLAY, is created.

This interface should include (inherit) the universal interface ZANDERS_IF_OBJ_INIT, which is used to initialize the class object.

Normally, the next step is to create classes that implement the interface.

So three classes are created(only two classes show in the above picture as the limitation of Visio page size).

There are three rules for the class:

  1. The insatiability of a class should be protected or private.
  2. The friend of a class should be ZANDERS_CF_OBJECT.
  3. The class should not have a constructor method.

Take the class ZANDERS_CL_BUS1 as an example, its configuration is as following picture shows.

Rule%201%20for%20class

Rule 1 for class

Rule%202%20for%20class

Rule 2 for class

Rule%203%20for%20class

Rule 3 for class

Finally, let’s use these classes in a program ZANDERS_TEST_BUS1.

Report%20code

Report code

In line 17 there is a call to the static method: zanders_cf_object=>inst_by_infname

to get the interface instance.

This method has three parameters:

  1. IV_INFNAME is the interface name.
  2. IS_IFKEYS is the universal business key,.e.g, Company Code, and Fiscal Year for finance.

Here, for simplicity and generality, I only use two components KEY1 and KEY2 to represent key fields.

3. CV_DETAILS is the class object initialization data.

Note that this is a changing parameter.

It means it is possible to change the initialization data as needed.

Let’s go into the coding details of this method.

Coding%20details

Coding details

The logic can be divided into two parts:

  1. Part 1 is for the unit testing injection.
  2. Part 2 is for the interface instance creation.

It has three steps to get the class name.

  • By replacing the string ‘IF_’ with ‘CL_’ to get the default class name.
  • By reading the customizing table ZANDERS_IF_IMPL to get the static design-time configuration name.
  • By calling the BADI ZANDERS_IF to get the dynamical run-time name.

when the class name is determined finally, the instance will be created in line 34.

In line 35, the interface method ZANDERS_IF_OBJ_INIT~INIT will be called to do the initialization for the class instance.

When using BADI ZANDERS_IF, you can not only determine the class name but also change the initialization data cv_details.

Running program ZANDERS_TEST_BUS1 with KEY1 = 1, KEY2 = 3, you will get a clear view of how this BADI works.

Components of this design pattern

The required developing components for this design pattern:

  • The cross-client customizing table ZANDERS_IF_IMPL.

Customzing%20table

Its key fields have two parts:

  1. IMPLIFNAME is the interface name.
  2. KEY1 and KEY2 are the universal business keys.
  • The BADI ZANDERS_IF with KEY1 and KEY2 are used as filters.

BADI

BADI

  • The universal interface ZANDERS_IF_OBJ_INIT for all class instance initialization.
  • The universal factory class ZANDERS_CF_OBJECT for all interface creation.
  • The universal injector class ZANDERS_CF_OBJECT_I for all unit testing.

How to apply it

If you would like to apply this design pattern to your application, please follow the following steps.

  • Determine the universal keys for your business scenario.
  • Create the required components in your namespace and replace KEY1 and KEY2 with your keys.
  • Create your interface.
  • Create your classes(Protected instance) which implement the interface.
  • Add your factory class as a friend of classes(in my example it is ZANDERS_CF_OBJECT).
  • Anywhere you use this interface, you have to use the factory class to get the instance.
  • In unit testing, use your injector class(my example is class ZANDERS_CL_BUS1_ENH)

Summary

This design pattern is based on two common object-oriented technologies,i.e., interfaces and factory class.

It also combines the ABAP programming feature to provide a universal and flexible way to choose the right run-time class.

It provides a universal way of injecting the test double interface instance to make your unit testing work easy.

Finally, I hope you have understood it clearly.

5 Comments
You must be Logged on to comment or reply to a post.
  • Hello,

    I proposed a similar idea once in the following blog

    https://blogs.sap.com/2017/04/17/charlie-and-the-chocolate-factory-pattern/

    I was not thinking about unit testing at the time, just a way to create the correct subclass based on dynamic business variables, just as you are doing.

    Early in 2018 I followed the SAP online course about unit testing which was wonderful. They suggested using “dependency lookup” in your productive code so as to make unit testing easy.

    https://blogs.sap.com/2018/04/14/sap-open-course-unit-testing-week-5-dependency-lookup/

    To me that was a fantastic idea. I started using it the next week and have never looked back.

    I would be really interested in your opinion on both of the concepts in the blogs I have put links to in this comment,

    Cheersy Cheers

    Paul

  • Hi Paul,

    You created two wonderful blogs!

    After reading your design, I have two questions:

    For the factory pattern in https://blogs.sap.com/2017/04/17/charlie-and-the-chocolate-factory-pattern/.

    If I understand correctly, every class should implement the method ZIF_CREATED_VIA_OCP_FACTORY=>IS_THE_RIGHT_CLASS_GIVEN to achieve dynamic class determination.

    Question 1: It seems to move some factory class functionality to the interface implementation class.

    For the factory method ZCL_OCP_FACTORY=>RETURN_OBJECT_GIVEN, the parameter IT_CONTEXT_DATA is used for candidate class check.

    Question 2: It seems there is no parameter to do the initialization for the class.

    How%20to%20inistilize%20it%3F

    How to initialize it?

    /
    How%20to%20inistilize%20it%3F
  • Hello!

    For the first one – the question is how a concrete implementation of a class should describe its’ single responsibility. You could have a configuration tale to say what class looks after what, but it just seemed right tome to have the class itself say what it does. You could have each concrete class have public constants to describe the area of responsibility, but I tried to make it as flexible as I could. There may be a better way, I am sure there is, and if so I would love to know what it is.

    For the second one – in subsequent iterations I discovered the need to pass in a parameter table if the constructor requires such. Or maybe you means something else by initialization?

    BTW I use such a factory in real life and it is so easy just to create a new country specific subclass and not have to change the core application in any way. I did that just last week and am always amazed at how easy it is.

    Cheersy Cheers

    Paul

  • Hi Paul,

    Thanks for the answers.

    There are a variety of methods for implementing the factory design pattern.

    It is up to the developer to choose the loved implementation way.

     

    Best regards,

    Anders