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.

Randa Khaled

Randa Khaled

Author Since: November 19, 2020

0 0 votes
Article Rating
Subscribe
Notify of
0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x