
041: Modern, solid and testable ABAP Code (Part 2)
The digital version of the betterCode presentation on modern and testable ABAP code. We'll look at software architecture and give tips for using ABAP Units.
Table of contents
In the second episode, we will continue with the presentation and look at the remaining patterns that we discussed last time but didn't finish. Therefore, we'll start directly with the next patterns.
Factory Method
The Factory method is intended to solve the actual problem. We create a static CREATE method that returns an object of the interface type as a result. This allows us to ensure that the correct type is returned and we no longer need to perform casting. In the class, we set the instantiation to PRIVATE, so the class can only be created via the CREATE method and no longer directly via the constructor. Furthermore, we have the option to perform preliminary work in the CREATE method, calling additional methods after the instance has been created and, for example, performing initialization. The method is called CREATE because it creates a new instance of the object and returns it to the caller. No further adjustments are necessary here. When using the factory method, we only need to bind the instance and can then, for example, use the method chaining functionality directly afterward. In a second example, we call the Get-Timestamp method directly after the instance has been created. We chain the method call to directly obtain a new timestamp without having to first store the instance locally in a variable. This chaining makes it relatively easy and manageable to use this object.
Factory
The Factory is intended to solve the actual problem of the Factory method. With the conventional Factory method, we often have the problem that we are still bound to the actual implementation of the class. This makes mocking the instance relatively difficult and replacing the actual implementation almost impossible. To solve this, we place the actual implementation in a private global class. This class is not intended to be used directly from outside, but is only accessed via the interface. The class instantiation is set to PRIVATE. Since we don't provide a CREATE method in the class itself to create the object, we use a global friend here. Through this friend reference, we authorize the actual factory class to create the object in the system. This doesn't change the rest of the implementation. The actual factory now takes over the CREATE method, as we already know it from the factory method. The class creates an instance of our actual implementation and returns it as the interface type. We set the factory class itself to ABSTRACT so that no instances can be created from it. We also set the class to CREATE PRIVATE. This ensures that only the static CREATE method can be called to create a new object. Direct access to or instantiation of the factory is therefore excluded, as this is not required. The usage and invocation are then very similar to the actual factory method, because we can also perform method chaining or have a new instance returned to us. The only difference is that we do not work directly with the implementation, but always use the factory to obtain an object. This decouples the caller from the concrete class or implementation.
Singleton
Let's take a closer look at the Singleton pattern as the last one. The Singleton, as the name suggests, is responsible for always returning exactly one instance, ensuring we always have the same instance. This helps us later during processing. We don't need to hold the instance in a local variable at runtime; instead, we simply call the method repeatedly to obtain the exact same instance. However, this requires that we are in the same session. The difference from a factory method is that we don't have a CREATE method here, but rather a method called GET_INSTANCE. This returns an instance, either to an interface or, as in this case, to the actual class, since we only have one implementation. Additionally, there is a static attribute that the Singleton holds. The `GET_INSTANCE` method first checks whether the singleton has already been created. If not, we create a new instance; otherwise, we return the existing instance. This ensures that we are working with the same instance system-wide. In the example shown, we obtain an instance and store a timestamp in it. Then, we create a new instance using the `GET_INSTANCE` method, which is technically still the original instance. Now, let's mistakenly assume that we have an empty instance. However, if we compare the two instances and their timestamps, we will find that they are exactly the same timestamp, even though we didn't execute a storage method on the second call. This is because we always receive the identical instance via the Singleton pattern. This means that the change we made in the first step is already present in the second call. Therefore, the timestamps are identical.
Examples
To better understand the patterns, let's look at them directly in the standard. We have selected some examples that already exist in the SAP standard. We are referencing an ABAP environment that runs in the cloud and provides corresponding APIs. We will now examine these various APIs in detail, define the different patterns on them, and take a close look at the respective implementation. We will very likely also encounter deviations that do not conform to the shown standard or usage.
Let's first look at a static class. The class CL_ABAP_CONTEXT_INFO is an important class because it provides us with information from system fields in ABAP Cloud, such as user data, system data, or times. If we look at the class in outline view, we notice that there are many types declared for external use, since it is an API. In this context, every method is also defined as static, which is why instantiation is not necessary. It is also noticeable that there is no constructor and therefore no instantiation is provided for. Basically, the class is still set to CREATE PUBLIC, meaning that instances could theoretically be created.
The second class is an instance-based class for zipping binary files. In the outline, we find the constructor; this already makes it a very good representation of an instance class and shows that it is intended to be used as an instance. What we don't see is an interface. Therefore, no uniform interface or blueprint for creation has been defined. Certain attributes, such as constants that are publicly available, are static, but this is due to the nature of the constant type. Furthermore, there are many private methods intended for outsourcing coding and internal management.
The third class is an implementation for an Application Job. This is evident primarily from the use of an interface to implement the individual methods. However, upon closer inspection of the specific implementations within the methods, we see that only rudimentary information is present. Since we are working with an interface implementation, we must fully implement each method of the interface within the class. This is particularly evident in the last method, which contains no actual executable code but includes a pragma to indicate its necessity. Because the class includes the suffix "Base" Since the name contains a , we assume it's a basic implementation that's always used when no more specific implementation exists, which is why it's implemented empty here.
The next class is a factory method. We can see this from the fact that we have a static CREATE method with the exact same signature as the constructor. Instance creation has been set to PRIVATE, so no instances can be created directly using the NEW operator. In the CREATE method implementation, we see that we create a new object and pass the parameters one-to-one. We then return this object using the RETURNING parameter. This creates an instance of the class, which is the typical use case for a factory method.
In the next example, we'll look at a factory. We see that, firstly, it already has the term "Factory" in its name. Secondly, it again has a static method that creates the instances and a corresponding interface that handles the implementation. The "Create-Instance" method creates instances of the class, in this case, the current class itself. This means that if we look at the object more closely, we see a hybrid implementation of a factory method and an actual factory. Creation is also set to PRIVATE here. Since the individual methods in the lower part are directly implemented, we can assume that this is actually a factory method and not, as the class name suggests, a factory. Therefore, it is particularly important to name objects correctly and use them according to their use case.
Finally, let's look at the Singleton. In this case, we have a class with a GET_CONTAINER method. This method manages the corresponding singleton. In the private attributes, you'll see a reference to the container type; this is our singleton instance. In the GET_CONTAINER method, we always first check if the singleton has already been created. If not, we create a corresponding object using a factory, store it in the attribute, and from then on, only return this single instance. The class also has an INITIALIZE method, which allows us to reset the singleton, for example, to start a new processing cycle. However, two critical points stand out here: First, it's possible to mistakenly create another instance of the class, which makes no sense for a singleton. Secondly, the GET_CONTAINER method name does not indicate that we will receive a singleton, but only uses the actual class name.
Outlook
With this, we have examined the various object-oriented patterns in detail and seen how they build upon one another and represent corresponding further developments of the actual type. It was particularly important for us to consider the SAP standard to show that these patterns are used consistently there. We can directly see from the objects what the actual purpose of the object is and how it is intended to be used in the context of RAP, Fiori Elements, or the Core Data Services. In the next installment, we will delve into the topic of ABAP Units and examine the patterns in detail, as well as the advantages they offer for testing. We will discuss the specific features and then look at automated testing using various tools in the standard. Until then... thanks for watching and see you next time.
Further References:
YouTube - Part 2
GitHub - Examples