英文原文:SAP-samples · 上游版本 d43eb13c4c7c · 原始 Markdown
ABAP Examples Using Object-Oriented Design Patterns
- ABAP Examples Using Object-Oriented Design Patterns
- Example Setup and Running Example Classes
- ABAP Unit Test Double Injection Techniques
- Abstract Factory
- Adapter
- Bridge
- Builder
- Chain of Responsibility
- Command
- Composite
- Data Access Object (DAO)
- Decorator
- Facade
- Factory Method
- Fluent Interface
- Flyweight
- Getters and Setters
- Iterator
- Mediator
- Memento
- Multiton
- Observer
- Prototype
- Proxy
- Singleton
- State
- Strategy
- Template Method
- Visitor
This ABAP cheat sheet includes a selection of design patterns you may have come across in object-oriented programming. It serves as an excursion. Unlike other ABAP cheat sheets, it does not cover ABAP-specific topics as such. Instead, it includes ABAP code explorations and experiments related to various design patterns.
Design patterns address common software design challenges, aiming to improve modularity, scalability, reusability, and more. The examples largely draw inspiration from design patterns established by the Gang of Four (GoF), applicable across different object-oriented programming languages. Apart from selected, renowned GoF patterns, the sheet also includes a random selection of other techniques and approaches.
[!IMPORTANT]
- The focus of the examples - which are also available via a separate repository branch (oo_patterns) - in the ABAP cheat sheet is on basic conceptual considerations regarding the design patterns, and experimenting with ABAP syntax and concepts (such as interfaces, abstract classes, encapsulation, and more). The examples aim to illustrate basic design pattern ideas using simplified ABAP demo classes.
- The examples are meant for exploration, experimentation, and demonstration, using non-semantic and non-real-world contexts to reduce complexity and give a rough idea. Due to their experimental nature, these examples do not represent best practices or model approaches, as various approaches and class setup strategies may apply. It is up to you to evaluate whether the patterns are suitable and beneficial for your setup. Always create your own solutions. The cheat sheet does not go into the details regarding the patterns' origins, purposes, real-world examples, similarities, differences, combinations of patterns, pros, or cons.
[!TIP] The cheat sheet includes copy and paste example classes. These are included in the oo_patterns branch of the repository. Find more information here regarding the import.
Example Setup and Running Example Classes
Most examples are set up for easy exploration using simple, (mostly) self-contained ABAP classes, i.e. there is only one class pool including local classes/interfaces instead of multiple global classes/interfaces:
- Global class (Global Class tab in ADT):
- Includes the
if_oo_adt_classruninterface to run the class with F9 in ADT. - Serves as the client that makes use of the local classes to demonstrate design patterns. Largely, the declarations and implementations in the CCIMP include are relevant for the conceptual considerations.
- Includes the
- CCIMP include (Local Types tab in ADT):
- Contains various local classes/interfaces to demonstrate design patterns, allowing quick copying and pasting without creating multiple global classes.
In the following sections, click the expandable sections for further descriptions and example code. To try the examples out, create a demo class named zcl_demo_abap and paste the code into it (Global Class and Local Types tabs in ADT). After activation, choose F9 in ADT to execute the class. The examples are set up to display output in the console.
[!NOTE]
- Some examples use artifacts such as database tables from the
mainbranch of the ABAP cheat sheet GitHub repository. The oo_patterns branch of the repository also contains the example classes. This branch is designed to function independently from themainbranch (for example, the said, few database tables are duplicated and renamed). Therefore, you can clone theoo_patternsbranch without also cloning themainbranch, or clone theoo_patternsbranch without dependencies (such as name conflicts with already existing artifacts, in case the artifacts of themainbranch have already been imported).- Disclaimer
ABAP Unit Test Double Injection Techniques
The following sections cover injection techniques that can be applied in the context of ABAP Unit. As described here, multiple techniques exist for injecting test doubles into a class under test to ensure their usage during test execution. Also find more information in the ABAP Unit Tests cheat sheet.
Backdoor Injection
- A sample setup to inject test doubles, as illustrated in the example, may look as follows:
- A dependent-on component (DOC) is identified (for example, reading data from a database table).
- Ideally, an interface to the DOC is available (here, a local interface is used). A data provider class implements this interface for the data provisioning (here, a local class is used).
- The class under test has been prepared. This example uses:
- A private instance attribute declared with a reference to the interface.
- The method to be tested calls a method via this interface reference variable for the data retrieval.
- The class's instance contructor assigns the interface reference variable by creating an object of the data provider class, which implements the interface.
- The test class may be implemented as follows:
- Since private attributes are not accessible in local test classes, the local test class is declared as a local friend of the global class.
- In the test method, an object of the class under test and a test double are created. The test double is declared with a reference to the interface (here, a local class is used that creates a test double). The private attribute is then assigned the test double, realizing the backdoor injection.
- The effect is as follows: When the method to be tested is called "regularly" (for example, when executing the class using F9, as in the example case), the object refers to the "regular" data provider (in this example, it is of type
lcl_data_provider), which means it uses production data. However, when the unit test is executed (for example, by choosing Ctrl/Cmd + Shift + F10), the object refers to the test double (in this case, of typeltd_test_data), which is the local test double that gets injected.
🟢 Click to expand for more information and example code
Unlike other examples, this example class has a different setup:
- It focuses on ABAP Unit tests. You can indeed execute the simple example class by choosing F9. To run the unit test, use Ctrl/Cmd + Shift + F10.
- The example class includes code from the global class and the CCIMP include, as well as the CCDEF (Class-relevant Local Types tab in ADT) and CCAU (Test Classes tab in ADT) includes.
- The example uses a demo database table (and a class to populate it) from the ABAP Cheat Sheet GitHub repository.
| Class include | Code |
|
Global class |
|
|
CCDEF include (Class-relevant Local Types tab in ADT) |
|
|
CCIMP include (Local Types tab in ADT) |
|
|
CCAU include (Test Classes tab in ADT) |
|
Constructor Injection
- Here, the test double is passed as a parameter to the instance constructor of the class under test.
- A sample setup to inject test doubles, as illustrated in the example, may look as follows:
- A dependent-on component (DOC) is identified (for example, reading data from a database table).
- Ideally, an interface to the DOC is available. A data provider class implements this interface for the data provisioning.
- The class under test has been prepared. This example uses:
- A private instance attribute declared with a reference to the interface.
- The method to be tested calls a method via this interface reference variable for the data retrieval.
- The class's instance contructor is declared with an optional parameter referencing the interface. When an object of the class is created, the interface reference variable is assigned appropriately, i.e. using the "regular" data provider or the test double.
- The test class may be implemented as follows:
- Since private attributes are not accessible in local test classes, the local test class is declared as a local friend of the global class.
- In the test method, an object of the class under test and a test double are created. The test double is declared with a reference to the interface (here, a local class is used that creates a test double). By creating an object of the class under test, the test double is passed as parameter of the instance constructor.
- The effect is as follows: When the method to be tested is called "regularly" (for example, when executing the class using F9, as in the example case), the object refers to the "regular" data provider (in this example, it is of type
lcl_data_provider), which means it uses production data (an object is created in the instance constrctor implementation accordingly). However, when the unit test is executed (for example, by choosing Ctrl/Cmd + Shift + F10), the object refers to the test double (in this case, of typeltd_test_data), which is the local test double that gets injected via the constructor. In the instance constructor implementation, the instance attribute is assigned the reference to the test double.
🟢 Click to expand for more information and example code
Unlike other examples, this example class has a different setup:
- It focuses on ABAP Unit tests. You can indeed execute the simple example class by choosing F9. To run the unit test, use Ctrl/Cmd + Shift + F10.
- The example class includes code from the global class, the CCIMP and CCAU (Test Classes tab in ADT) includes.
- The example uses a demo database table (and a class to populate it) and a global interface from the ABAP cheat sheet GitHub repository.
| Class include | Code |
|
Global class |
|
|
CCIMP include (Local Types tab in ADT) |
|
|
CCAU include (Test Classes tab in ADT) |
|
Parameter Injection
- Here, the test double is passed as a parameter to the tested method (i.e. an optional importing parameter) in the class under test.
- A sample setup to inject test doubles, as illustrated in the example, may look as follows:
- A dependent-on component (DOC) is identified (for example, reading data from a database table).
- Ideally, an interface to the DOC is available (here, a local interface is used). A data provider class implements this interface for the data provisioning (here, a local class is used).
- The class under test has been prepared. The method to be tested:
- Calls a method via an interface reference variable for the data retrieval.
- Is defined with an optional parameter. It is typed with reference to the interface.
- Includes a check whether the optional parameter is bound.
- Note: To use the local demo interface in the signature, the method is defined in the private visibility section.
- The test class may be implemented as follows:
- Since private attributes are not accessible in local test classes, the local test class is declared as a local friend of the global class.
- In the test method, an object of the class under test and a test double are created. The test double is declared with a reference to the interface (here, a local class is used that creates a test double).
- The effect is as follows: When the method to be tested is called "regularly" (for example, when executing the class using F9, as in the example case) without passing the optional parameter, an object of the "regular" data provider (in this example, it is of type
lcl_data_provider) is created following the evaluation of theIFstatement, which means it uses production data. However, when the unit test is executed (for example, by choosing Ctrl/Cmd + Shift + F10) and the optional parameter is supplied, the parameter is bound and, thus, uses the test double (in this case, of typeltd_test_data), which gets injected via the parameter.
🟢 Click to expand for more information and example code
Unlike other examples, this example class has a different setup:
- It focuses on ABAP Unit tests. You can indeed execute the simple example class by choosing F9. To run the unit test, use Ctrl/Cmd + Shift + F10.
- The example class includes code from the global class and the CCIMP include, as well as the CCDEF (Class-relevant Local Types tab in ADT) and CCAU (Test Classes tab in ADT) includes.
- The example uses a demo database table (and a class to populate it) from the ABAP Cheat Sheet GitHub repository.
| Class include | Code |
|
Global class |
|
|
CCDEF include (Class-relevant Local Types tab in ADT) |
|
|
CCIMP include (Local Types tab in ADT) |
|
|
CCAU include (Test Classes tab in ADT) |
|
Setter Injection
- Here, the test double is passed as a parameter to a setter method.
- A sample setup to inject test doubles, as illustrated in the example, may look as follows:
- A dependent-on component (DOC) is identified (for example, reading data from a database table).
- Ideally, an interface to the DOC is available (here, a local interface is used). A data provider class implements this interface for the data provisioning (here, a local class is used).
- The class under test has been prepared. This example uses:
- A private instance attribute declared with a reference to the interface.
- The method to be tested calls a method via this interface reference variable for the data retrieval.
- The class's instance contructor assigns the interface reference variable by creating an object of the data provider class, which implements the interface.
- A setter method is declared which expects a reference to the interface to be passed. To use the local demo interface in the signature, the setter method is defined in the private visibility section.
- The test class may be implemented as follows:
- Since private attributes are not accessible in local test classes, the local test class is declared as a local friend of the global class.
- In the test method, an object of the class under test and a test double are created. The test double is declared with a reference to the interface (here, a local class is used that creates a test double). The setter method is called, passing the test double. Following the setter method implementation, the interface reference variable is assigned the test double.
- The effect is as follows: When the method to be tested is called "regularly" (for example, when executing the class using F9, as in the example case), the object refers to the "regular" data provider (in this example, it is of type
lcl_data_provider), which means it uses production data. However, when the unit test is executed (for example, by choosing Ctrl/Cmd + Shift + F10), the object refers to the test double (in this case, of typeltd_test_data), which is the local test double that gets injected via the setter method.
🟢 Click to expand for example code
Unlike other examples, this example class has a different setup:
- It focuses on ABAP Unit tests. You can indeed execute the simple example class by choosing F9. To run the unit test, use Ctrl/Cmd + Shift + F10.
- The example class includes code from the global class and the CCIMP include, as well as the CCDEF (Class-relevant Local Types tab in ADT) and CCAU (Test Classes tab in ADT) includes.
- The example uses a demo database table (and a class to populate it) from the ABAP Cheat Sheet GitHub repository.
| Class include | Code |
|
Global class |
|
|
CCDEF include (Class-relevant Local Types tab in ADT) |
|
|
CCIMP include (Local Types tab in ADT) |
|
|
CCAU include (Test Classes tab in ADT) |
|
Test Double Injection Using Inheritance and Method Redefinition
- This ABAP Unit test double injection technique makes use of inheritance and redefining methods.
- Here, a local test double class is created by redefining a method of the class under test.
- A sample setup to inject test doubles, as illustrated in the example, may look as follows:
- A dependent-on component (DOC) is identified (for example, reading data from a database table). Unlike the other ABAP Unit examples, there are no local or global interfaces involved.
- The global class that includes a method to be tested is not declared as a final class, allowing for the creation of subclasses. For simplicity, this global class implements a method specifically for the test. You can imagine that a data retrieval method is contained in another global class, which the class under test uses.
- The test class may be implemented as follows:
- A test double class is created that inherits from the global class and redefines the method identified as the DOC, enabling the provisioning of test data for the test classes.
- The test method does not instantiate the class under test. Instead, it creates an object of the test double class. The method to be tested is then called through the test double's reference variable.
- The effect is as follows: When the method to be tested is called "regularly" (for example, by executing the class using F9), it runs as usual with production data. However, when the unit test is executed (for example, by choosing Ctrl/Cmd + Shift + F10), the redefined method in the test class is called, injecting and using the test double.
🟢 Click to expand for example code
Unlike other examples, this example class has a different setup:
- It focuses on ABAP Unit tests. You can indeed execute the simple example class by choosing F9. To run the unit test, use Ctrl/Cmd + Shift + F10.
- The example class includes code from the global class and the CCAU include (Test Classes tab in ADT).
- The example uses a demo database table (and a class to populate it) from the ABAP Cheat Sheet GitHub repository.
| Class include | Code |
|
Global class |
|
|
CCAU include (Test Classes tab in ADT) |
|
Abstract Factory
- The pattern may be used when you need to create a set of related objects (a family of "products") that work together or are processed together for specific purposes.
- Such families of related objects may be required in different variants. However, the created objects must be compatible with each variant.
- Here are some examples to illustrate this:
- Consider a car manufacturer. Multiple components are needed to assemble a car, such as the chassis, engine, and equipment (certainly, there are more components). These components form a family of related objects to create a car. All cars from the manufacturer follow the same setup: each requires a chassis, engine, and equipment. However, the manufacturer offers various car models like convertibles, sedans, SUVs, and pickup trucks. When producing these different variants, specific components may vary. The assembly must only use compatible objects. For example, when producing a convertible, a chassis with a sedan roof should not be used.
- Consider a restaurant offers various three-course menus (this is the example used by the demo classes). Menu items include starters, main dishes, and desserts, forming a family of related objects. Different variants exist to create specific menus. For example, the restaurant offers a vegan menu. When creating the vegan menu, it should be ensured the menu does not include a beef steak as a main dish or dairy products in desserts.
- In terms of code, you may need a setup to create related objects in an organized and consistent way. The abstract factory design pattern enables this by using a high level of abstraction in your class setup, i.e. it enables the creation of related objects that belong to the same family through abstractions like abstract classes, without bothering about their specific implementations and allowing for the creation of objects with appropriate types determined at runtime. A factory, such as an abstract factory class, sets up the object creation process by specifying methods that provide objects. Concrete factories then inherit from the abstract factory class and implement the methods to create specific kinds of objects. More abstraction is involved, as outlined in the example description below.
- Some of the benefits of the pattern include: centralizing multiple object creations in one location, ensuring consistency and compatibility, simplifying object creation for users by hiding complexity, adding or modifying different variants without affecting existing code, providing flexibility and adaptability (however, adding new products might be cumbersome as it requires changes in many parts of the code).
- The abstract factory pattern differs from the factory method design pattern in several ways. Both facilitate object creation through abstraction. The factory method uses a single interface as an abstraction layer and single factory methods, while the abstract factory pattern uses a higher degree of abstraction and multiple factories. The factory method pattern primarily creates single objects, whereas the abstract factory pattern involves creating multiple related objects.
🟢 Click to expand for more information and example code
Example Notes
The example uses the following class setup in the CCIMP include (Local Types tab in ADT) to illustrate the abstract factory pattern:
-
Setting up abstract classes for products to establish a template for concrete products
- Abstract classes for the products (abstract products)
- In this example, the
starters,main_dishes, anddessertsclasses serve as abstract classes for products. - These classes provide a common interface for concrete products, ensuring all variants are created consistently.
- In this example, the
- Concrete classes for the products (concrete products)
- In the example, concrete classes for the products, inheriting from the
starters,main_dishes, anddessertsabstract classes, are named using the formatstarters_*,main_dishes_*, anddesserts_*. - For each variant, such as the seafood menu, specific concrete products are provided. Here, the method implementations of the concrete classes involve adding various dishes to a string table, representing available menu options for customers. Numbers in parentheses (1-3) denote the menu course. Generally, the implementations must ensure that the products fit their specific variant.
- In the example, concrete classes for the products, inheriting from the
- Abstract classes for the products (abstract products)
-
Setting up an abstract factory and concrete factories
- Abstract factory class
- The abstract factory, represented by the
menu_factoryclass, defines a common interface for product families to create related objects. - It includes factory methods to provide these related objects.
- The returned related objects are typed with references to the abstract products (
starters,main_dishes, anddesserts).
- The abstract factory, represented by the
- Concrete factory classes
- The variants enter the picture with concrete factories. Here, the concrete factories are represented by the
italian_menu_creator,seafood_menu_creator, andvegan_menu_creatorclasses. - Each variant has a concrete factory, and these factories inherit from the abstract factory class
menu_factory. - The concrete factories include factory methods to create and return concrete products at runtime. Note that the returning parameter is typed with reference to the abstract product.
- This implementation ensures compatibility, meaning the seafood factory should only create and provide objects for starters, main dishes, and desserts related to seafood.
- The variants enter the picture with concrete factories. Here, the concrete factories are represented by the
- Abstract factory class
-
Setting up a client
- The
menu_providerclass represents the client. - The client uses the abstract factories for object creation, allowing it to work with any variant without dealing with concrete factories.
- In this example, the implementation returns the entire menu (
create_menumethod) based on the selected menu variant. The "selection" of the menu variant is realized by the instance constructor of the class. The desired menu variant is passed to the instance constructor as an object reference, typed with reference to the abstract factory. - At runtime, the abstract factories connect with concrete factories so that the
create_menumethod implementation can work with them. Regardless of which variant is chosen (i.e., which object reference is passed), the client can handle it, use compatible objects, and return the desired menu. - The example includes the helper class
customer_order, which has a factory method to create the appropriate object reference required by the client, based on the desired menu.
- The
Global class:
- The global class implements the
if_oo_adt_classruninterface and calls methods from local classes. - It serves as a vehicle for demonstrating the design pattern. The declarations and implementations in the
CCIMPinclude are relevant for the conceptual considerations. - Using the
menu_providerclient class and specifying the variant using thecustomer_orderclass, a menu is created. During the method calls, all related objects (products) are created, and string tables are filled with various dishes to illustrate the pattern. As a result, a single string table is returned, containing meals for all three courses per variant offered by the restaurant. The example includes method calls for all three example variants and outputs the resulting string tables to the ADT console.
| Class include | Code |
|
Global class |
|
|
CCIMP include (Local Types tab in ADT) |
|
Adapter
- Used when APIs are incompatible, and you want to integrate those APIs into an existing one to consolidate all functionality.
- This can be achieved by using an adapter class to convert and transform functionality from a non-compatible API into the existing API.
🟢 Click to expand for more information and example code
Example notes:
- This example demonstrates the adapter design pattern with the following declarations and implementations:
- Global class:
- Implements
if_oo_adt_classrunand calls methods from local classes. - Acts as a consumer of APIs (local classes) defined in the CCIMP include.
- Implements
- CCIMP include (Local Types tab in ADT):
- The example is similar to the factory method example.
- Local interface
lif_hello: Defines an enumeration type and a method that returns a string. - Local classes
lcl_**implementlif_hello. - Local class
lcl_hello_factory: Contains the factory methodcreate_hello. The class specifiesCREATE PRIVATEto prevent external instantiation. Thecreate_helloreturns a reference of typeREF TO lif_hello. - Assuming functionality is extended. An existing API is integrated and reused.
- This is represented by the
lcl_de_xstringclass. It implements a different interface. The method returns a data object of typexstring, which is not compatible with thesay_hellomethod of thelif_hellointerface that returns astring. - An adapter class is introduced that implements the
lif_hellointerface and handles conversion (in the example case,xstringis transformed tostring) to allow the use of the non-compatible API. - Users can then call methods of the adapter class.
- Global class:
- The class execution includes the following:
- Multiple objects are created using the factory method with different input parameters.
- Based on the parameter, a specific object is created. The
say_hellomethod returns a string as implemented in the resepctive class. - When
lif_hello=>deis supplied, an object is created using the adapter class.
| Class include | Code |
|
Global class |
|
|
CCIMP include (Local Types tab in ADT) |
|
Bridge
- The idea behind the bridge design pattern is to categorize the class setup into an abstraction layer and an implementation layer, keeping them separate.
- The abstraction defines high-level functionality, while it delegates actual operations to the implementation layer. The bridging element enters the picture like this: The abstraction may hold a reference to an instance of the implementation side, enabling interaction through that instance.
- The following example uses a car production scenario for demonstration. The abstraction layer is represented by a car (a base class, and potentially refinements - classes inheriting from the base class - for specific types, such as an SUV or a sports car) that outlines high-level production steps. The implementation layer is represented by car factories that execute these steps (a base class for factories, and classes inheriting from the base class for various factories). Find more information about the example in the expandable section.
- The bridge design pattern may be beneficial when you can apply a decoupling of classes and determine different class hierarchies for the abstraction and implementation layers, still requiring them to work together flexibly, unlike relying on, for example, inheritance within a single, large class hierarchy:
- You might avoid an overcrowding of classes. For example, without the bridge pattern, you would need a separate class for each combination, such as a class for car type A with factory Z, car type A with factory Y, car type B with factory Z, car type B with factory Y, and so on. The bridge design pattern, however, may allow flexible combinations of the two layers at runtime.
- Without decoupling, maintenance and extensions may become inflexible. By using the bridge design pattern, you can easily add a new car type (that is, a new refined abstraction) without needing to modify the car factory classes on the implementation side, as the setup is made to have the layers independent from each other. Vice versa, adaptations on the implementation side may occur independently, without disrupting the code on the abstraction side.
🟢 Click to expand for more information and example code
Example notes:
- The example uses a car production context for demo purposes.
- The abstraction layer is represented by a car (class
lcl_carand its refinements). The implementation layer is represented by car factories that perform various production steps (classlcl_car_factoryand its concrete classes for different factories). - The bridging element enters the picture with
lcl_carholding an instance referencing a concrete factory, and delegating implementation-specific steps to the car factory. - The example code demonstrates the bridge design pattern by including the following declarations and implementations:
- CCIMP include (Local Types tab in ADT):
lcl_car- This class represents the base class of the abstraction layer.
- It is an abstract class that defines high-level functionality by specifying methods for various car production steps.
- It specifies the instance constructor that subclasses must call, expecting an object of
lcl_car_factoryfrom the implementation layer to be passed. - In this example, the non-abstract method
produceorchestrates the production steps. For display purposes, a string table with comments on the production steps is filled throughout the method calls, which is returned by the returning parameter. - The protected visibility section specifies additional components, such as the
factoryinstance attribute, which serves as the bridging element in the setup by holding an instance of a concrete car factory. - The non-abstract methods
paint,control_quality, andtest_driveprovide default implementations. These methods correspond to those declared by thelcl_car_factory, with method calls delegated to concrete implementations via thefactoryreference variable. - The example is designed to include refined abstractions, allowing subclasses of
lcl_carto either use the default implementations without modifications or refine the production steps with specific implementations. In some cases, the simplified example just involves adding strings to a string table to represent different, refined implementations. However, the default implementation is always used by calling the method withsuper->. - The
car_typemethod is abstract, requiring subclasses to implement it. In this example, its purpose is to determine the car type, which is also an attribute oflcl_car(cartype), not of the car factory classes.
lcl_compact_car,lcl_suv,lcl_sports_car- These classes inherit from
lcl_carand represent refined abstractions, that is, different car types that can be produced in every factory available. - Each class can, but does not have to, override the default implementation or add specific implementations.
- The example demonstrates specific implementations in some cases by adding strings to a string table, which represents different implementations. However, the default implementation is always used by calling the method with
super->. For example, the assumption is that there are additional prerequisites for test-driving a sports car, such as needing access to a special test track. - By allowing these classes to extend the default implementation, these refined abstractions illustrate how adaptations can be made flexibly without dependencies on other abstractions or the implementation layer.
- These classes inherit from
lcl_car_factory- Represents the base class of the implementation layer.
- It is an abstract class that defines methods that inheriting classes must implement.
- In the example setup, the method names correspond to those defined by
lcl_car. The methods inlcl_car"know" about these methods but call them through the stored instance infactory. They do not know the concrete implementation details; they simply delegate to those methods.
lcl_factory_a,lcl_factory_b,lcl_factory_c- These classes inherit from
lcl_car_factoryand represent the concrete implementations that perform operations in the implementation layer. - Each class must redefine the abstract methods defined in the superclass
lcl_car_factory. - The simplified example adds a string to a string table, representing the unique operations performed by each car factory class. While the steps and functionalities, such as painting, quality checks, and test driving, remain consistent, the implementation may vary. An assumption might be that, for example, the painting is done using different machines in the car factories, or specific test tracks are used for the test drive.
- Because all factory classes use the same abstract interface, individual cars (
lcl_carobjects) can be used interchangeably with any factory.
- These classes inherit from
lcl_log- Not related to conceptual considerations.
- It just serves display purposes by providing a string table to hold the production comments added throughout the method calls.
- Global class:
- Implements the
if_oo_adt_classruninterface and calls methods from local classes. - Serves as the client in the example. It is responsible for associating objects of the abstraction layer with objects of the implemenation layer.
- By calling the non-abstract method
produce, the production process is orchestrated accordingly. - The string table that is output visualizes the production steps.
- Implements the
- CCIMP include (Local Types tab in ADT):
| Class include | Code |
|
Global class |
|
|
CCIMP include (Local Types tab in ADT) |
|
Builder
- The idea is to simplify the creation of complex objects, which may involve multiple steps, by hiding this complexity from users.
- Depending on requirements or user input, the class setup should allow for the creation of different objects that share similar or the same creation steps and attributes.
- The builder design pattern may be useful here. Builder classes consolidate the necessary steps to create objects and return specific ones, allowing users to get the objects without taking care of the exact process.
- By using the builder pattern, you can flexibly create different objects that follow the same or similar building processes. Builder classes also centralize code changes, and you can add more builder classes for additional use cases or objects.
- The example tries to demonstrate the builder design pattern with the following class setup:
- An abstract builder class defines the steps for object creation and includes methods and attributes shared by concrete builder classes responsible for creating specific objects. The example does not use an interface because the abstract class includes non-abstract methods performing general tasks not requiring object-specific implementations.
- Multiple concrete builder classes inherit from the abstract superclass. They (can) follow the same object creation flow when redefining the superclass methods, but the implementation may vary among builder classes to serve different purposes for specific objects.
- Another class orchestrates object creation. This class contains factory methods and acts as an object provider for external users (the global class in the example), simplifying object creation and hiding complexity. Users call a factory method, providing certain input parameters. Based on these values, specific objects are created by dedicated concrete builder classes. The orchestration class ensures methods are called in the correct sequence and returns the final created object.
🟢 Click to expand for more information and example code
Example notes:
- Purpose: The example represents an automatic internal table creator and table content creator using random values.
- Global class:
- Implements the
if_oo_adt_classruninterface and calls methods from local classes. - Acts as a consumer of APIs, i.e. local classes defined in the CCIMP include.
- Implements the
- CCIMP include (Local Types tab in ADT):
lcl_itab_builder:- An abstract builder class defining instance attributes and abstract methods. Concreate builder classes inherit from this superclass.
- It is not an interface because it includes attributes and non-abstract methods for general tasks used by subclasses. These methods are in the public visibility section because the global example class also demonstrates their functionality.
- The common building steps (i.e. the abstract methods) are:
build_type_info: Checks if the provided type is available.build_components: Retrieves component names and their types (e.g.,string,i,decfloat34).build_table_keys: Builds or verifies a list of primary table keys used for internal table creation, depending on whether a custom key list is supplied or default keys are used. The latter retrieves key components specified centrally in the DDIC, such as when DDIC database tables are used as type names.build_table_type: Builds a type description object based on the line type, table kind, and keys.build_data_object: Creates the internal table. For flexibility, it is created as an anonymous data object (TYPE REF TO data) using a dynamicCREATE DATAstatement and the created type description object.build_random_data: Adds table entries to the created internal table, using nonsense random content for components with elementary types. Methods for retrieving random values are implemented in the abstract superclass.
lcl_builder_*classes:- Concrete builder classes, such as
lcl_builder_std_itab_w_prkey, that inherit from the abstract superclass. - These classes implement the abstract methods of the superclass, representing the step-by-step building process to create specific objects.
- The example includes concrete builder classes that create standard tables with non-unique primary keys and empty keys, as well as sorted and hashed tables.
- Concrete builder classes, such as
lcl_itab_data_provider: A concrete builder class inheriting from the abstract builder class, used to populate existing internal tables with demo data. An internal table is passed as an actual parameter in a method described next.lcl_itab_provider:- Orchestrates the step-by-step object building process and provides the objects to users to hide complexity and guide creation.
- Called by users, which is the global class in the example.
- Contains two factory methods:
create_itab: Used for internal table creation based on input parameters. It expects a line type name: globally available line types, such as DDIC database table or CDS entity names, or globally available elementary types for simple internal tables. Optionally, you can specify key component names for the target table; if component names not in the structure are used, they are ignored. If not specified, default key components are used to create tables with primary keys. Optionally, you can specify the number of table entries to create. The method returns an object with the table available via an instance attribute.populate_itab: Supplies existing local internal tables with nonsense random demo data. The method returns an object with the populated table available via an instance attribute.
- Note:
- This example is for experimentation and exploration. It does not claim to cover all aspects of the design pattern or internal table creation/population.
- Excluded aspects include: Limited exception handling (a failing
ASSERTstatement is available in some cases), secondary table key handling, proper handling of key uniqueness, fixed or enumerated values, deep or nested components (such as references, nested structures, and tables), uppercase letters for character-like types, unnecessary population of the client field, returning the internal table in an output parameter (the table is available in an instance attribute of typeREF to data). - The example uses repository objects from the ABAP cheat sheet repository. If you have not imported it, you can replace the
Z*artifacts with others for exploration.
| Class include | Code |
|
Global class |
|
|
CCIMP include (Local Types tab in ADT) |
|
Chain of Responsibility
- The idea behind the chain of responsibility design pattern is organizing handlers in a sequence (a chain), allowing for flexible request processing, potentially without knowing which handler will handle the request at compile time.
- Requests move through this chain of handlers until one is found that can process it.
- A potential setup, illustrated in the following example, may look like this:
- An interface defines methods for processing requests.
- An abstract handler class that implements the interface, includes default behavior, and maintains a reference to the next handler in the chain.
- Concrete handler classes that inherit from the abstract handler, deciding whether to process the request or pass it to the next handler.
- A client that establishes the chain of responsibility and sends requests.
- This pattern may be useful for creating flexible and maintainable setups, especially when processing various types of requests differently, managing multiple handlers without knowing in advance which one will actually handle the requests, following indeed a specific processing sequence, enabling easy extension by adding new concrete handlers, and more.
🟢 Click to expand for more information and example code
Example notes:
- The example demonstrates the chain of responsibility design pattern through the following declarations and implementations. Note that the example is simplified, and various class setup strategies may apply.
- As example context, it uses the retrieval of type information (RTTI) based on type description objects. For more details on RTTI and type description objects, refer to the Dynamic Programming cheat sheet. Different handler classes handle specific type description objects, including handlers for elementary, structured, and table types. Classes that can handle other types are not implemented. They should be handled by a default handler class.
- Global class:
- Implements the
if_oo_adt_classruninterface and calls methods from local classes. - Acts as the client to demonstrate the design pattern. The declarations and implementations in the CCIMP include are relevant for conceptual considerations.
- Implements the
- CCIMP include (Local Types tab in ADT):
- Interface
lif_chain_of_resp:- Defines the shared interface for concrete handler classes in the chain of responsibility.
- Includes the
processmethod for processing type description objects and returning type information (mutliple pieces of information are stored in a string table for display purposes), and theset_handlermethod for assigning the next handler in the chain.
- Class
lcl_base_handler- Represents an abstract handler class that provides default implementations for concrete handler classes inheriting from it.
- Since it is defined as an abstract class, it cannot be instantiated directly. Only its subclasses can be instantiated.
- The class maintains a reference to the next handler in the protected
iref_handlerattribute. - By implementing the
lif_chain_of_respinterface, it provides the methodslif_chain_of_resp~set_handlerfor setting the next handler andlif_chain_of_resp~processfor processing requests.
lcl_concrete_handler*classes- Represents concrete handler classes that inherit from the
lcl_base_handlerclass. - These classes provide specific implementations for handling different types of type description objects.
- The example includes handler classes for processing elementary, structured, and table types. Other types are not addressed in detail.
- If a class cannot handle the request (because the type description object has another type), it calls the next handler in the chain. For types not covered by the example, the default handler class
lcl_concrete_handler4can manage requests and provide general information (no casting is implemented to obtain more specific type information as in other concrete handler classes).
- Represents concrete handler classes that inherit from the
- Interface
- The purpose of the example is to illustrate the following:
- The client, represented by the implementations in the global class, determines the chain of responsibility by setting the concrete sequence of handler calls and starting requests.
- These requests (represented by passing a type description object) are processed by handler class objects.
- Each handler in the chain attempts to process the request. If a handler can handle the respective type description, it processes it. Otherwise, it delegates the request to the next handler in the chain.
| Class include | Code |
|
Global class |
|
|
CCIMP include (Local Types tab in ADT) |
|
Command
- The idea behind the command design pattern is to wrap operations to be executed and the details required for the operations inside individual objects.
- By placing all details in these command objects, you can, for example, store these objects, pass them as arguments of method calls, execute them in a specific order, or delay their execution to a later time.
- Typically, objects invoking commands (invokers) are separated from the objects that receive the commands and finally execute the operations (receivers). Basically, invokers can execute any command without knowing the execution details defined by the receivers.
- The pattern may be useful for creating a flexible setup that can be easily extended by adding new commands, without requiring a modification of existing classes. By storing commands, the setup can also support undo and redo functionalities.
- A potential setup, illustrated in the following example, may look like this:
- Command interface (or an abstract class)
- Defines a method for executing commands.
- The method can be defined without any input parameters to have a generic approach for various kinds of operations. The actual data to work with can be assigned to instance attributes of command objects.
- Concrete command classes
- These classes implement the command interface.
- They execute the command by calling appropriate methods implemented by receiver objects, i.e. the actual operation is performed by the receivers.
- Receiver class
- This class contains the implementation for executing the operations defined by the commands.
- Methods in the receiver class are called by the concrete command classes.
- Invoker class
- Responsible for triggering command execution.
- Invokers are unaware of the implementation details.
- Client
- Creates command objects and connects them with suitable receiver objects.
- Command interface (or an abstract class)
🟢 Click to expand for more information and example code
Example notes:
- The example demonstrates the command design pattern at a high level using the following context:
- Assume there is a simplified image editor app.
- The app may offer various functionalities, such as resizing and rotating images.
- In the app, a user edits an image by triggering resizing and rotation - each operation represents an individual command object - by providing specific values, such as width and height to resize the image.
- Additionally, the app supports undo and redo functionality.
- The code demonstrates the design pattern with the following declarations and implementations:
- CCIMP Include (Local Types Tab in ADT):
- Command interface
lif_command- Defines a method for executing commands (
execute) and a method for reverting executed commands (undo).
- Defines a method for executing commands (
- Receiver class
lcl_receiver- Executes commands by implementing specific methods. In the image editor example, it includes methods like
resizeandrotateto edit images and change object states based on commands. - The instance constructor initializes the image's values, which are provided during object creation for simplicity.
- The
get_current_statemethod is implemented for logging and display purposes.
- Executes commands by implementing specific methods. In the image editor example, it includes methods like
- Concrete command classes
lcl_cmd_resizeandlcl_cmd_rotate- Implement the command interface.
- Objects of these classes represent specific commands.
- Each object of the classes holds a reference to the receiver (stored in the
recattribute), passed via the constructor. Details of the command are stored in instance attributes (wd,ht,ang), which the operation uses for execution. - The
executemethod calls appropriate methods through the receiver objects. - The
undomethod reverts changes by storing previous values in instance attributes during theexecutemethod.
- Invoker class
lcl_invoker- Executes commands through the
exec_cmdmethod. - In this example, the command object is passed as an argument to the instance constructor.
- It includes internal tables that hold references for undo and redo operations (
undo_tabandredo_tab). When executing a command viaexec_cmd, a reference is added toundo_tab, andredo_tabis cleared. Theredomethod executes the last command fromredo_taband adds it toundo_tab.
- Executes commands through the
- Command interface
- Global class
- Implements the
if_oo_adt_classruninterface and calls methods from local classes. - Acts as the client.
- Creates objects of the receiver, invoker, and commands.
- Command objects are created with references to the receiver and the necessary execution details.
- Implements the
- CCIMP Include (Local Types Tab in ADT):
| Class include | Code |
|
Global class |
|
|
CCIMP include (Local Types tab in ADT) |
|
Composite
- The composite design pattern may be useful when you need to work with objects that form hierarchical, tree-like structures.
- The composite refers to a component that can contain subordinate components in the tree-like structure. Subordinate components can be leaves (individual components without subordinate components) or other composites (that can have further subordinates).
- Addressing a composite typically means addressing its subordinate objects as well. In terms of coding, when a method is called on a composite object, it delegates processing to its subordinate objects. If a subordinate object has its own subordinates, the operation is further delegated. This approach allows operations to be performed across the entire object hierarchy, on individual composites (including their subordinates), or on single leaf objects.
- This setup enables clients to uniformly address objects within the hierarchy, typically through a common interface implemented by all components. It also allows for flexible extension by adding composites and leaves without affecting existing code.
- A potential setup, illustrated in the following example, may look like this:
- A component interface that defines common operations shared by all implementing classes.
- A leaf class that implements the component interface and whose objects represent individual objects without subordinates.
- A composite class whose objects represent more complex objects, consisting of subordinate objects, which may be leaves or other composites, forming the tree-like structure. Like leaf classes, composite classes implement the component interface, typically delegating processing to subordinate objects to, for example, combine results.
- With the common interface, clients can uniformly address the objects, no matter if interacting with the entire hierarchy, composites, or individual leaf objects. Clients need not be aware of dealing with a composite or leaf object.
🟢 Click to expand for more information and example code
Example notes:
- This example tries to illustrate the composite design pattern using an organizational hierarchy with managers and employees as demo context.
- The example code demonstrates the composite design pattern by including the following declarations and implementations:
- CCIMP include (Local Types tab in ADT):
lif_org- Represents the component interface that defines operations for both leaf nodes (individual employees) and composite nodes (managers with subordinates).
- Among the operations are:
get_salary: Returns the individual or total salary (for leaf nodes, the value is the same; for composite nodes, it adds the manager's salary to the salaries of subordinates).get_headcount: Returns the count of individual members (for leaf nodes, the count is 1; for composite nodes, it is 1 plus the count of all subordinates).get_employee_hierarchy: Returns the organizational structure as a string table with proper indentation.get_data: Returns employee data.set_manager/get_manager: Sets or gets the reference to the manager node.get_employees_by_role: Returns employees filtered by job role.get_employees_by_office: Returns employees filtered by office location.get_employee_info: Returns JSON with employee information and reporting line.assign_training: Assigns training.
- The type
ty_employee_datadefines the structure for employee information. Thett_orgtable type is used to collect organization members.
lcl_employee- Represents the leaf class and implements the component interface.
- Objects of this class represent individual employees in the organizational hierarchy who have no subordinates.
- They store employee information (
employee_data) and a reference to the manager (mngr) as instance attributes.
lcl_manager- Represents the composite class for managers who can have subordinates.
- Objects of this class represent managers in the organizational hierarchy who can have both kinds of subordinates, employees and other managers.
- Like objects of the
lcl_employeeclass, objects oflcl_managerstore employee information (of the manager, who is also an employee) and a reference to their manager (a higher-level manager). - Because composites can have subordinates, the
subordinates_tabtable holds references to those subordinates. - In addition to the shared interface methods, there are composite-specific methods:
add_subordinate: Adds an employee or manager as a subordinate and establishes the manager reference.remove_subordinate: Removes a component from the subordinates.get_subordinates: Returns all direct subordinates.
- Further notes on the example:
- The client can uniformly interact with the entire organization, departments (represented by heads of department managers), sub-managers, or individual employees due to the common interface.
- Several methods reflect the composite design pattern by implementing recursive method calls. For example, they accumulate headcount and salary numbers across the hierarchy, build the hierarchy visualization recursively, filter employees recursively, and assign training recursively.
- Most example method implementations reflect moving down the hierarchy. Since both leaf and composite objects maintain references to their managers, the example also includes a sample implementation for moving up the hierarchy. The information returned by the
get_employee_infomethod, formatted in JSON, includes details about the reporting line. - The
remove_subordinatemethod removes a component from the subordinates. The example implementation specifies that if a manager (a composite) is removed, their subordinates are reassigned to the next higher-level manager.
- Global class:
- Implements the
if_oo_adt_classruninterface and calls methods from local classes. - Serves as the client in the example. Here, the client creates and manages the hierarchy.
- Individual objects are created for both managers as composites and employees as leaf nodes. Each object is instantiated with specific employee data (ID, name, job role, salary, office). The hierarchical structure is established using the
add_subordinatemethod to build the organization tree. The CEO represents the root node, with department heads as subordinates, each of whom may have further subordinates. - The example implementation demonstrates the following operations:
- Retrieving the employee hierarchy for the entire organization and specific departments.
- Retrieving employees by role and office for the entire organization and specific departments.
- Assigning trainings to all employees, to all employees of a specific department, or to a single employee.
- Retrieving employee information as JSON, including the reporting line.
- Retrieving headcount and salary information for the entire organization, specific departments, and a specific employee.
- Removing subordinates, which is only possible for managers within their downward reporting line. After removal, the entire organization hierarchy and headcount is retrieved and displayed to demonstrate successful removal and, if a manager (a composite) was removed, the reassignment of subordinates.
- Throughout the implementation, the example demonstrates that the client can uniformly interact with the hierarchy by calling the same methods. Depending on the operation, it can be performed on the entire organization (starting with the CEO), the engineering department (starting with the head of engineering), the sales department (starting with the head of sales), and so on.
- Implements the
- CCIMP include (Local Types tab in ADT):
| Class include | Code |
|
Global class |
|
|
CCIMP include (Local Types tab in ADT) |
|
Data Access Object (DAO)
- A Data Access Object (DAO) provides an API for performing CRUD operations on a database table.
- By providing an object-oriented approach for these operations, you can centralize and encapsulate standard operations, eliminating the need to write them throughout the code.
- A potential setup, illustrated in the following example, may include:
- An interface that defines methods for accessing database tables.
- A class that implements the interface. The example DAO class here uses the singleton design pattern to provide users with a single object of the DAO class.
🟢 Click to expand for more information and example code
Example notes:
NOTE
The example uses a demo database table (zdemo_abap_carr) from the ABAP cheat sheet repository.
- This example tries to illustrate the Data Access Object (DAO) design pattern using the following declarations and implementations:
- CCIMP include (Local Types tab in ADT):
- Interface
lif_dao- Describes a DAO for the database table
zdemo_abap_carr, providing a single API to interact with it. - Defines CRUD methods to interact with the database table, including
count_entriesto return the number of entries,does_entry_existto check if a record with a specific key exists,get_entryto return a single row for the specified key,get_all_entriesto return all rows in an internal table,create_entryto insert a single row,create_entriesto insert multiple rows from an internal table,upsert_entryto insert or update a row, anddelete_entryto delete a row by key.
- Describes a DAO for the database table
- Class
lcl_dao- Implements the
lif_daointerface and is implemented as a singleton. CREATE PRIVATEprevents direct instantiation of the class. Users can obtain a reference to the interface (stored in the static attributeoref) via the staticget_daomethod.- The methods use ABAP SQL
SELECT,INSERT,UPDATE, andDELETEstatements.
- Implements the
- Interface
- Global class:
- Implements the
if_oo_adt_classruninterface and calls methods from the local classlcl_dao. - Acts as the client in the example.
- The implementation illustrates all operations available through the interface methods.
- Implements the
- CCIMP include (Local Types tab in ADT):
| Class include | Code |
|
Global class |
|
|
CCIMP include (Local Types tab in ADT) |
|
Decorator
- Enhances and modifies existing objects by wrapping them in other objects (objects of decorator classes) without modifying the original object setup and class code.
- Useful when flexibility is needed to handle various combinations and functionalities of objects.
- An exemplary class setup may include:
- An interface that defines common functions
- A base class implementing the interface; objects of this class are meant to be enhanced (decorated)
- An abstract decorator class that delegates functionality enhancement
- Concrete decorator classes that enhance existing objects
- Objects of the base class can be wrapped in concrete decorator objects, enhancing functionality since all objects share features by implementing the interface
- Advantages:
- Users can decorate objects with any decorator since all implement the interface.
- All classes implement the interface, allowing all objects to be accessed through the same interface reference variable.
- Users can work with any base class object, be it a direct object of the base class or a decorated object.
- The pattern offers flexibility as you can dynamically enhance objects using any decorators to achieve any desired object.
- Maintenance can be simplified because each decorator class encapsulates specific functionality, and you can easily extend functionality by adding more decorator classes.
🟢 Click to expand for more information and example code
The following example demonstrates the decorator design pattern in a pizza creation context. Starting with a basic pizza (object), you can create various pizzas (decorate the pizza object) by adding ingredients, which also changes the pizza's cost:
- Global class:
- Implements the
if_oo_adt_classruninterface and calls methods from local classes in the CCIMP include. - Demonstrates the decorator design pattern by creating an
lcl_basic_pizzaobject. This object is enhanced (decorated) by wrapping it in objects of concrete decorator classeslcl_decorator_*. - You can run the class by choosing F9 in ADT. The example displays output in the ADT console.
- Implements the
- CCIMP include (Local Types tab in ADT):
lif_pizza- An interface defining a common setup for all decorators and decorated objects.
- Implemented by all other local classes in the example, including the concrete base class and decorator classes.
- Defines the methods
create_pizzafor pizza creation andget_costsfor cost calculation.
lcl_basic_pizza- Objects of the class represent basic objects that can be decorated.
- Implements the
lif_pizzainterface. - Defines the instance
constructorthat specifies an integer as importing parameter to set the base cost.
lcl_pizza_decorator- Represents an abstract decorator class.
- Implements the
lif_pizzainterface. - As an abstract class, object creation is restricted to its subclasses (the concrete decorator classes).
- The instance constructor receives the decorated pizza object, linking concrete decorators to the object being decorated. This reference is assigned to an interface reference variable in the protected section, allowing subclass access (though not relevant for this example). Subclasses call the constructor.
- The implemented interface methods delegate calls to the wrapped/decorated object using the interface reference variable.
lcl_decorator_*classes- Represent concrete decorator classes.
- Inherit from the abstract decorator class
lcl_pizza_decorator. - Their objects can wrap and enhance existing objects.
- Since
lcl_pizza_decoratorimplementslif_pizza, thelcl_decorator_*classes can also redefine the interface methods. - The superclass
lcl_pizza_decoratordefines an instance constructor expecting a reference (REF TO lif_pizza) as an importing parameter. It is mandatory that the subclasses call the superclass's constructor, yet it is not mandatory for the constructor to be explicitly defined and implemented. The subclasses in the example explicitly define and implement the instance constructor. You can, for example, check out thelcl_decorator_salamiclass, and remove the definitionMETHODS constructor IMPORTING pizza TYPE REF TO lif_pizza.and its implementation. There will not be syntax errors when creating objects in the global class usinglcl_decorator_salami. Due to inheritance, a reference is still required to be passed for the importing parameter although not explicitly specified in the local subclass. - Implementations of interface methods in these classes make simple modifications, like adding ingredients and adjusting total costs. Each concrete decorator class introduces its own functionality, allowing dynamic stacking of decorations. The
super->...calls trigger the calling of superclass implementations. Since the link to the decorated object is estrablished, ingredients are added to the existing ingredients, and costs are added to the existing costs.
| Class include | Code |
|
Global class |
|
|
CCIMP include (Local Types tab in ADT) |
|
Facade
- Assume you must handle complex functionality that may involve creating objects from multiple classes and calling methods, maybe even in a specific sequence and involving certain dependencies.
- The facade design pattern's purpose is to provide a simplified API, hiding the complexity from users and allowing them to achieve desired results without dealing with and knowing about underlying complexities.
- Users can interact with this straightforward API instead of handling intricate steps and details.
🟢 Click to expand for more information and example code
Example notes:
- The simplified example uses a travel search context. Users receive information about available flights, hotels, and rental cars. This information is retrieved by creating instances of multiple classes and various method calls. A facade class provides a simplified API that manages complex underlying functionality. Users interact only with the facade class, eliminating the need to handle individual class objects directly.
- The example demonstrates the facade design pattern with the following declarations and implementations:
- Global class:
- Implements the
if_oo_adt_classruninterface and calls methods from local classes. - Serves as a vehicle for demonstrating the design pattern. The declarations and implementations in the
CCIMPare relevant for the for conceptual considerations. - The global class represents the user of the simplified API the facade class offers.
- Implements the
- CCIMP include (Local Types tab in ADT):
- Contains multiple local classes (
lcl_*) that provide information about available flights, hotels and rental cars. - The simple classes include a method that calls a method in another class. Information is retrieved based on origin, destination, and travel time span.
- The
lcl_travel_facadeclass serves as the facade class. Theplan_travelmethod includes object creations and method calls. For simplicity, the method returns information about available flights, hotels, and rental cars in a string table.
- Contains multiple local classes (
- Global class:
- The class execution involves the following:
- The global class contains several method calls to the facade class.
- Based on the parameters passed, travel options are evaluated. As a result, a string table is output showing travel search information.
| Class include | Code |
|
Global class |
|
|
CCIMP include (Local Types tab in ADT) |
|
Factory Method
- Used, for example, to:
- Provide users with an object of a class instead of them creating the objects themselves.
- Control and simplify class instantiation for external users.
- Offer a stable API for class users, so they only need to call one stable method. This way, the code may be modified or extended, and the changes to the class do not affect users.
- Typically, using the
CREATE PRIVATEaddition in a class definition prevents object creation outside the class. A factory method, usually a static method, then supplies users with class objects. You can also include input parameters in the factory method to control instantiation. - Example of a predefined ABAP class with factory methods:
CL_ABAP_REGEX.
🟢 Click to expand for more information and example code
Example notes:
- This example demonstrates the factory design pattern with the following declarations and implementations:
- Global class:
- Implements the
if_oo_adt_classruninterface and calls methods from local classes. - Serves as a vehicle for demonstrating the design pattern. The declarations and implementations in the
CCIMPare relevant for the for conceptual considerations.
- Implements the
- CCIMP include (Local Types tab in ADT):
- Defines the
lif_factoryinterface, specifying a method that is implemented in all classes that implement the interface. - Contains multiple local classes (
lcl_**) implementinglif_factory. Each class returns a string in its method. - Class
lcl_factory_clcontaining a factory method:- Defined as
CREATE PRIVATEto prevent object creation outside the class. - Offers the
create_hellofactory method returning an interface reference. This method uses an input value to create the appropriate object, which the reference points to. Here, the input is an enumerated type that is defined in the interface.
- Defined as
- Defines the
- Global class:
- The class execution includes the following:
- Multiple objects are created using the factory method with different input parameters.
- Based on the parameter, a specific object is created. The
say_hellomethod will return a string as implemented in the resepctive class.
| Class include | Code |
|
Global class |
|
|
CCIMP include (Local Types tab in ADT) |
|
Fluent Interface
- Enables method chaining
- Achieves method chaining by returning a reference to the current object. There may be variations in the implementation, for example, the returned object may be a modified copy or the modified original.
- Such a design may consolidate method calls for a simpler, more readable code flow, instead of having individual method calls.
🟢 Click to expand for more information and example code
Example notes:
- This example demonstrates the fluent interface design pattern with the following declarations and implementations:
- Global class:
- Implements
if_oo_adt_classrunand calls methods from local classes. - Acts as a consumer of APIs (local classes) defined in the CCIMP include.
- Implements
- CCIMP include (Local Types tab in ADT):
- Example 1 (String building)
- Local interface
lif_string_processing:- Defines multiple methods for string modification
- Most of the methods specify a reference to the interface as returning parameter
- Local class
lcl_string_processing:- Specifies the
CREATE PRIVATEaddition to prevent instantiation from outside the class - However, as
lcl_stringis declared as friend,lcl_stringcan instantiate the class. - Implements the interface
- The method implementations return a modified copy of the original object
- Specifies the
- Local class
lcl_string:- Contains a static factory method returning an instance of
lcl_string_processing(the returning parameter is typed withTYPE REF TO lif_string_processing) - The factory method requires a string to be supplied, which represents the base string that can be modified using the methods that
lif_string_processingoffers
- Contains a static factory method returning an instance of
- Local interface
- Example 2 (Simple calculations)
- Local class
lcl_calc:- Represents a simpler example of the fluent interface pattern
- Instantiable class
- Methods of the class return an object reference of the class
- Local class
- Example 1 (String building)
- Global class:
- The class execution includes the following by demonstrating chained method calls:
- Example 1: Multiple instances are created showing the variety of methods the interface offers for modifying a string (adding strings to strings, precedings strings with strings, splitting strings into a string table, performing replacements, transforming to lowercase and uppercase, reversing strings, inserting strings, removing spaces, retrieving the modified string)
- Example 2: Multiple instances are created performing consecutive calculations; note that there is no proper exception handling in the simple example (e.g. a zero division is just ignored)
| Class include | Code |
|
Global class |
|
|
CCIMP include (Local Types tab in ADT) |
|
Flyweight
- The flyweight design pattern may be useful when you have a large number of objects that share similar data. For example, these objects might have both unique and shared components. The idea behind the pattern is to avoid duplicating the shared components in each object by separating them for reuse. Such an approach can reduce the number of objects needed and reduce memory consumption.
- A potential setup, illustrated in the following example, may look like this:
- A flyweight interface that defines methods for processing shared parts.
- A concrete flyweight class that implements the interface to handle shared parts. Objects of this class represent shareable objects and contain the shared data.
- A flyweight factory class that manages existing flyweight objects with the shared data in an internal table. If a flyweight object is requested by the client, the table is searched, and if found, the object is returned. If a flyweight object does not yet exist, a new object is created, added to the internal table, and returned.
- The client uses the flyweight objects through the flyweight factory and passes unique states.
🟢 Click to expand for more information and example code
Example notes:
- The example demonstrates the flyweight design pattern through the following declarations and implementations. Note that the example is simplified, and various class setup strategies may apply.
- The context of this hypothetical example is as follows:
- There are many customers, each of whom can have an object created for them. Depending on events - such as subscriptions, orders, New Year, ect. - each customer should receive a specific message. Hypothetically, another event object is created for this purpose and for each customer, but the event objects (the messages) are identical across all customers and stay consistent. Customer object and such an event/message object go together so that specific messages can be sent for specific customers. The trivial example uses simple strings, but it is assumed that the objects to be created involve large data sets and consume a considerable amount of memory.
- In this context, the message represents the shared data (as it can be reused for all customers), while customer-specific information (such as ID, name, and email) constitutes the unique data. In this example, the notification is represented by filling a string table.
- Instead of creating separate objects for each customer and event, reusable data like the notification message for different events is stored in flyweight objects.
- When a client wants to trigger a notification for a specific customer regarding an event, it is first checked whether a flyweight object containing the shared data already exists. If it does, the existing object is returned and used instead of creating a duplicate. Otherwise, a new flyweight object is created, added to a reference storage (an internal table), and returned.
- Global class (Global Class tab in ADT):
- Implements the
if_oo_adt_classruninterface and calls methods from local classes. - Acts as the client to demonstrate the design pattern. The declarations and implementations in the CCIMP include are relevant for conceptual considerations.
- The client requests flyweight objects via the factory class, and provides unique states to the method that handles the unique and shared data.
- Implements the
- CCIMP include (Local Types tab in ADT):
- Interface
lif_flyweight:- Defines the common interface for concrete flyweight classes.
- Includes the
notifymethod for flyweight classes to implement, which accepts a reference to a customer object and returns text in a string table.
- Class
lcl_customer:- Holds the unique state of each customer.
- The
get_customer_infomethod retrieves unique customer information that is to be connected with the shared data, with thecustomer_infoattribute storing detailed customer information.
- Class
lcl_flyweight:- Represents concrete flyweight objects.
- Implements the
lif_flyweightinterface. - Holds shared data via the
textattribute and processes notifications through thenotifymethod.
- Class
lcl_flyweight_factory:- Provides access to flyweight objects stored in an internal table (
flyweight_tab). - Checks for existing flyweight objects. If one exists, it returns that object to ensure reuse. Otherwise, it creates a new object, adds it to the internal table, and returns it.
- Provides access to flyweight objects stored in an internal table (
- The example includes the filling of a log table, indicating whether a flyweight object is created or reused.
- Interface
| Class include | Code |
|
Global class |
|
|
CCIMP include (Local Types tab in ADT) |
|
Getters and Setters
- For encapsulation purposes, getters and setters are an object-oriented programming approach to control access to object-internal data.
- Getters provide read access through public methods that retrieve the content of private attributes, while setters offer write access through public methods that modify the content of private attributes. Setters may also include validation to ensure that only valid data is stored. In contrast, public attributes can be directly modified without validation.
- This ensures that data access occurs solely through these methods, creating a stable API for external users. Changes to the underlying implementation typically do not affect how users interact with the API.
- As an alternative to providing setter methods, if your use case requires creating an object whose internal data should not be modified after initialization, you might choose to initialize object data only through the constructor without providing setter methods.
🟢 Click to expand for information on an ADT quick fix for creating getters and setters
- The ABAP Development Tools for Eclipse (ADT) have a built-in option for creating getters and setters.
- Consider the following class skeleton with a private attribute called
str.
CLASS zcl_demo_abap DEFINITION
PUBLIC
FINAL
CREATE PUBLIC .
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
PROTECTED SECTION.
PRIVATE SECTION.
DATA str TYPE string.
ENDCLASS.
CLASS zcl_demo_abap IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
...
ENDMETHOD.
ENDCLASS.
- Place the cursor on
strand choose STRG + 1. - The quick fix menu will display various options, including Generate Getter and Setter for str (or options to create just a getter or a setter).
- Double-clicking the option should generate code similar to this:
CLASS zcl_demo_abap DEFINITION
PUBLIC
FINAL
CREATE PUBLIC .
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
METHODS: get_str RETURNING VALUE(r_result) TYPE string,
set_str IMPORTING i_str TYPE string.
PROTECTED SECTION.
PRIVATE SECTION.
DATA str TYPE string.
ENDCLASS.
CLASS zcl_demo_abap IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
...
ENDMETHOD.
METHOD get_str.
r_result = me->str.
ENDMETHOD.
METHOD set_str.
me->str = i_str.
ENDMETHOD.
ENDCLASS.
🟢 Click to expand for more information and demo code
Example notes:
- The example code demonstrates a getter and setter method through simple declarations and implementations. It uses an object to represent personal information that can be accessed and (partly) modified.
- Global class:
- Implements the
if_oo_adt_classruninterface and calls methods from a local class. - Acts as the external user that calls the getter and setter methods in the example.
- The implementation includes internal tables that store demo data, which are iterated over to show various scenarios of valid and invalid input. Initially, objects are created using demo data. If there is invalid input, such as incorrect dates or placeholders for first and last names, the objects are not created. If the input is valid, the objects are created and added to an internal table. This table of valid objects is then processed, with the getter and setter methods being called. The setter method is first called using a demo email address. If the email address is invalid, the object's state remains unchanged, and the email value is not assigned to the private instance attribute. Then, the getter method is called to return the internal data. If the email setting is successful, the data will include the email address, demonstrating that the object's state has been modified.
- Implements the
- CCIMP include (Local Types tab in ADT):
lcx_error:- Local exception class used for raising exceptions when creating an object with incorrect or initial data, as well as for email validation.
lcl_demo:- Definitions in the public visibility section:
- The instance constructor initializes the object by setting parts of the personal information (first name, last name, birthday). It validates input values. If they are invalid, it raises an exception to prevent object creation. If the values are valid, they are assigned to the private
p_infoattribute.- Note: The example design with the (partly) state setting through the instance constructor references the previously mentioned alternative approach.
- Getter method
get_personal_info: Returns the personal information stored in the privatep_infoattribute. - Setter method
set_email: Calls thevalidate_emailmethod to check the email format. If validation succeeds, theemailcomponent in thep_infostructure is updated. If it fails, an exception is raised.
- The instance constructor initializes the object by setting parts of the personal information (first name, last name, birthday). It validates input values. If they are invalid, it raises an exception to prevent object creation. If the values are valid, they are assigned to the private
- Definitions in the private visibility section:
- Includes the structured instance attribute
p_info, which stores the actual personal information data. validate_emailmethod: It checks the passed email against a regex pattern. It raises an exception if the email does not match the required format, ensuring data integrity by preventing changes to the object's state with invalid external input.
- Includes the structured instance attribute
- Definitions in the public visibility section:
- Global class:
| Class include | Code |
|
Global class |
|
|
CCIMP include (Local Types tab in ADT) |
|
Iterator
- The iterator design pattern may be useful for processing collections, represented by a group of objects, sequentially. It allows you to iterate across a group of objects without revealing internal details to the client or ensure that all objects of the collections are iterated (and, for example, avoid the processing of individual objects more than once).
- A potential setup, illustrated in the following example, may look like this:
- An iterator interface that defines operations for iterating across collections, such as retrieving individual elements, moving forward or backward, and checking for the end of the collection. The concrete iteration logic is implemented by concrete iterators that implement the interface. This approach enables flexible extension by adding new collections or iterators.
- Concrete iterators that implement the iterator interface contain specific logic for processing objects of a collection. These iterators may be associated with collections by passing collections to their instance constructors.
- A collection interface typically defines a method for creating/retrieving an iterator object linked to a specific concrete iterator.
- Concrete collection classes that implement the collection interface and include the actual collection of objects. They implement the iterator retrieval method and provide themselves as collection object linked to the iterator.
- Clients can retrieve the iterator and use it to iterate across the collection without concerning themselves with the internal details of the objects. They may also create items of the collections.
🟢 Click to expand for more information and example code
Example notes:
- The example code demonstrates the iterator design pattern by including the following declarations and implementations:
- CCIMP include (Local Types tab in ADT):
lif_iterator- This interface defines the operations for iterators that implement it.
- The interface includes the
get_nextandget_previousmethods for iterating across collections. Thehas_nextmethod checks for additional items to iterate over. Theget_current_positionmethod provides the current index, whileresetreturns to the start of the collection.
lif_collection- This interface specifies methods for managing collections.
- The
get_iteratormethod returns an iterator instance that allows access to the collection's items. - Additionally, the
get_number_of_itemsandget_item_at_positionmethods are included for further interaction options with collections.
lcl_iterator- This class implements the
lif_iteratorinterface and represents an iterator. - In the example implementation, the instance constructor initializes the iterator, linking it to a specific collection.
- It implements the methods of the
lif_iteratorinterface, providing the logic to process the collection's items. - The class is designed to support two sample implementations of concrete collections.
- This class implements the
lcl_collection_strings,lcl_collection_line2string- Both classes represent concrete collections and implement the
lif_collectioninterface. - The
get_iteratormethod returns an instance oflcl_iteratorlinked to the respective collection. - In the simplified example,
lcl_collection_stringsholds a string table, which can be iterated. The class allows adding strings to the collection using theaddmethod. lcl_collection_line2stringrequires an index table to be supplied via the instance constructor. This internal table is then iterated. The demo purpose of the example is to convert all table components to strings and return an entire table line as a comma-separated list. The implementation includes checks to ensure that all components are convertible to strings.
- Both classes represent concrete collections and implement the
- Global class:
- Implements the
if_oo_adt_classruninterface and calls methods from local classes. - Serves as the client in the example. The client creates or supplies collections and retrieves iterators to iterate over the collection items.
- The global class code demonstrates 3 example contexts:
- Example 1: Demonstrates
lcl_collection_strings. Several strings are added to the collection. The code shows various options the demo code offers, such as creating a collection, retrieving an iterator, retrieving the number of items in the collection, retrieving an item at a specific position, moving backward and forward within the collection, retrieving the current position, checking if there a more items to be processed, resetting, two separate iterators iterating across the same collection. - Example 2: Demonstrates
lcl_collection_line2string. Some demo internal tables are created and supplied for iteration. - Example 3: Demonstrates a released ABAP class used for processing XML data. For more information, refer to the Working with XML and JSON in ABAP cheat sheet. The class reflects an iterator setup. In the example, XML data is created, processed, item names and values are output, reflecting the iteration process.
- Example 1: Demonstrates
- Implements the
- CCIMP include (Local Types tab in ADT):
| Class include | Code |
|
Global class |
|
|
CCIMP include (Local Types tab in ADT) |
|
Mediator
- The mediator design pattern assumes that a group of objects interacts with each other, which can lead to complexity.
- Instead of direct interaction among these objects, a mediator serves as a central go to object that orchestrates and manages the interactions.
- The individual members of the group of objects only need to know about the mediator, which takes care of the necessary steps without requiring any awareness of other objects' implementations.
- This setup can be useful for avoiding tight coupling and dependencies, enabling greater flexibility and improved maintainability by centralizing the operational flow.
- A potential setup, illustrated in the following example, may look like this:
- A mediator interface that defines methods for interactions among members of a group of objects.
- A concrete mediator that implements the mediator interface and is responsible for orchestrating interactions between members. This can be achieved by storing references to the member objects in instance attributes.
- Members of a group of objects: These members need to interact with one another. They can do so by storing a reference to the mediator in all member objects, allowing interactions to occur through this mediator reference.
🟢 Click to expand for more information and example code
Example notes:
- The example uses a shopping scenario as demo context with several members of a group of objects (represented by shopping cart, payment processing, stock management, customer notifications) interacting with one another.
- The example code demonstrates the mediator design pattern by including the following declarations and implementations:
- CCIMP include (Local Types tab in ADT):
lif_mediator- Represents the mediator interface that declares methods for interaction among members of a group of objects.
- An enumerated type defines the kinds of events (
item_added,item_reserved,item_removed, etc.) - The
notifymethod is used to handle the communication between the members. It is called by member classes to inform the mediator. For this purpose, the method includes various importing parameters. Thelogmethod handles logging for display purposes.
lif_member:- Represents the interface for the member classes. It defines the
set_mediatormethod to link members and mediator. In the example setup, this method is called in the instance constructor of the concrete mediator to establish the linking.
- Represents the interface for the member classes. It defines the
- Member classes (Note: For simplicity, implementations in these classes are simulations.)
lcl_shopping_cart: Manages products in the shopping cart with methods for adding and removing products. Theget_cartmethod returns the current contents of the cart. The shopping cart itself is represented by a string table that holds the added products.lcl_payment: Processes payments for ordered products via theprocess_paymentmethod.lcl_stock: Manages the stock with methods for reserving and releasing products.lcl_notification: Sends notifications to customers.
lcl_mediator:- Represents the concrete mediator by implementing the mediator interface.
- Contains references to objects of all members of the group. In the example, these references are bound by calling the
set_mediatormethod in the instance constructor implementation. - Implements the
notifymethod for orchestrating the member interactions based on the provided enumerated type. It also logs activity records via thelogmethod. - Generally, the class manages the flow of the business logic by coordinating the operations and also their sequence. In the simplified example, the flow is as follows: When a product is added to the cart, the cart (that is, the member object of class
lcl_shopping_cart) notifies the mediator. Then, the mediator instructs the stock manager to reserve the product. Once reserved, the mediator notes this confirmation. If a product is removed, the mediator instructs the stock object to release the it. When the checkout is initiated, the mediator processes the payment. After the payment, the mediator instructs the notification objet to send a confirmation.
- Global class:
- Implements the
if_oo_adt_classruninterface and calls methods from local classes. - Serves as the client in the example. Here, the client creates the set of members of a group of objects and links them through a mediator object. The example implementation includes for the client to add or remove products to a shopping cart, and trigger the checkout process.
- Implements the
- CCIMP include (Local Types tab in ADT):
| Class include | Code |
|
Global class |
|
|
CCIMP include (Local Types tab in ADT) |
|
Memento
- The idea of the memento design pattern is to allow the storing and restoring of an object's state.
- The state is stored in a separate object, the memento, which captures the object's state.
- A potential class setup for this pattern may include a class for which mementos are created, a memento class, and a class to manage the mementos.
- Use cases for this pattern include saving object states and providing undo (and possibly redo) functionality to revert changes.
🟢 Click to expand for more information and example code
Example notes:
- The example demonstrates the memento design pattern with the following declarations and implementations:
- Global class:
- Implements the
if_oo_adt_classruninterface and calls methods from local classes. - Serves as a vehicle for demonstrating the design pattern. The declarations and implementations in the
CCIMPare relevant for the for conceptual considerations.
- Implements the
- CCIMP include (Local Types tab in ADT):
- Several class setup and design strategies may apply. This simplified example includes three classes:
lcl: Represents the class for which mementos are created.lcl_memento: Captures the state of anlclobject.lcl_memento_controller: Manages mementos.
- More details:
-
lclclass- Represents the class for which mementos are created.
- It is defined as
FINALandCREATE PRIVATE, which prevents inheritance and direct instantiation by other classes. This setup implements the singleton design pattern, allowing instantiation only through theget_objmethod. - Attributes:
state: Represents the state of anlclobject. In this simplified example, the state is a string that can change.oref: A static attribute holding a reference to the singlelclobject.
- Methods:
get_obj: Static method that returns a singleton instance of thelclclass.get_state: Returns the current state of the object.change_state: Updates the current state by appending text, with an optional leading space.save: Creates a memento of the current state with a specified tag.restore: Sets the current state to the state stored in the provided memento.
-
lcl_mementoclass- Captures the state of an
lclobject. - Marked as
FINALandCREATE PRIVATE, which prevents inheritance and direct instantiation by other classes, except for its friend (using theFRIENDSaddition) class,lcl. - Attributes:
state: Represents the state of anlclobject. In this simplified example, it is a string that can change.tag: A string that labels each memento for identification.time_stamp: Records the UTC timestamp when the memento is created. In this example, it is included for logging and display purposes.
- Methods:
constructor: Initializes thestateandtagattributes and captures the current timestamp.get_state: Returns the memento's stored state.get_tag: Returns the tag associated with the memento.get_timestamp: Returns the creation timestamp of the memento.
- Captures the state of an
-
lcl_memento_controllerclass- Manages mementos but does not modify their states.
- Attributes:
mementos_tab: Internal table storing all created mementos.redo_tab: Internal table storing mementos for potential redo operations.lcl_object: Holds a reference to thelclobject for which it manages mementos.
- Methods:
constructor: Initializes the reference variable for thelcl_object.save_memento: Adds a new memento to themementos_tab.undo: Restores thelclobject to the previous state and adds the latest memento to theredo_tab.redo: Restores thelclobject to the next state in theredo_tab, if available.restore_by_tag: Searches for a memento by its tag in themementos_taband restores thelclobject to that state, also returning information on the success of the restoration.get_memento_log: Returns logs of all mementos in themementos_tabandredo_tab.
-
- Several class setup and design strategies may apply. This simplified example includes three classes:
- Global class:
| Class include | Code |
|
Global class |
|
|
CCIMP include (Local Types tab in ADT) |
|
Multiton
- The multiton design pattern is a variant of the singleton pattern designed to manage a fixed set of distinct instances, rather than just one.
- While the singleton pattern ensures a class has only one instance across the entire application, returned through a factory method, the multiton pattern controls instantiation by managing multiple unique instances. These instances are differentiated by unique keys, allowing for controlled, instance-specific access.
- The design pattern suggests that a key is mapped to a specific instance in an internal table. A factory method then returns that instance when the corresponding key is provided. If an instance does not exist for the provided key, the factory method can create a new instance, and add it to the internal table.
- This approach is beneficial when you need to control the instantiation process without restricting it to just a single instance. It offers greater flexibility by allowing multiple controlled instances, each suited to handle different needs or contexts. Furthermore, it allows for instance reuse, which can be especially useful in data-intensive or complex scenarios.
🟢 Click to expand for more information and example code
Example notes:
- The following example illustrates the multiton design pattern with the following declarations and implementations:
- Global class:
- Implements the
if_oo_adt_classruninterface and calls methods from local classes. - Serves as a vehicle for demonstrating the design pattern. The declarations and implementations in the CCIMP include are relevant for the conceptual considerations.
- Represents the user who requires instances to work with.
- Implements the
- CCIMP include (Local Types tab in ADT):
- Class
lcl_multiton- Specifies the
CREATE PRIVATEaddition to prevent instance creation outside the class. - Maintains one instance per unique key. The key values in this example are determined by ABAP enumerated types, which serve as identifiers for unique instances. The example assumes that users require specific configuration settings among a set of multiple settings, with each setting corresponding to a distinct class instance.
- Instance maintenance occurs in the
instance_tableinternal table within the private visibility section. This table contains entries for created class instances, mapped to specific keys (the ABAP enumerated types). - The
get_instancemethod is a public, static factory method that returns an instance oflcl_multitonfor a given key. It first checks if an entry in theinstance_tabletable already exists. If not, it creates a new instance and adds it to the table. Finally, it returns the instance corresponding to the provided key, ensuring that users receive the specific instance they need. - The instance
constructoris declared in the private visibility section to prevent direct instantiation. The assumption is that instantiation may involve complex or data-intensive tasks, which should be avoided if users frequently require instances. The pattern promotes reuse and prevents duplicate instantiations. The simplified demo implementation only adds entries to an internal table for display purposes. - Other implementations in the demo class, like the
do_somethingdummy method, also serve display purposes. A string table collects information, including the current timestamp, to demonstrate multiple different entries added during class instantiation and method calls. Thelogtable tracks the number of instance retrieval requests and the timestamps of the first instantiation per key within the internal session.
- Specifies the
- Class
- Global class:
| Class include | Code |
|
Global class |
|
|
CCIMP include (Local Types tab in ADT) |
|
Observer
- The observer design pattern allows an object to notify other objects - the observers - whenever its state changes.
- A class setup may include the following (as implemented in the first code example):
- Observers can register or unregister to receive notifications (or not receive them anymore) about state changes of an observed object. The notification of observers may include parameters to be passed. The observed class may contain an internal table to store references to observers, along with methods for adding and removing those references. When a change occurs, a notification method can inform the observers by iterating through the internal table and calling a method that all observers implement through a common interface. This enables the observed object to communicate with all registered observers.
- All registered observers can react to the information received.
- Users of the setup are responsible not only for the object creation but also for the flexible registration and unregistration of observers.
- Such a setup can be useful when you want to avoid tightly coupling different objects while still providing a mechanism to transfer changes from a specific object to a random number of other objects.
🟢 Click to expand for more information and example code
Example notes:
- The example code includes two examples demonstrating the observer design pattern with the following declarations and implementations:
- Global class:
- Implements the
if_oo_adt_classruninterface and calls methods from local classes. - Serves as a vehicle for demonstrating the design pattern. The declarations and implementations in the
CCIMPare relevant for the for conceptual considerations.
- Implements the
- CCIMP include (Local Types tab in ADT):
- Example 1
- Defines the
lif_observerinterface that enables the observed object to communicate with observers via thenotifymethod, which expects two numbers as importing parameters, among others. Thecalculatemethod is common to all observers, performing a simple calculation based on the two integer values provided. lclrepresents the class for observed objects and offers several methods:set_numbers: Changes the state of the observed object by setting two integer values. The implementation includes calling thenotify_observersmethod.registerandunregisterare used to register and unregister observers. For that purpose, the internal tableobserver_tabis used to manage reference variables. When registering, references to observers are added. When unregistering, references are removed from the table.notify_observers: The method is responsible for notifying observers. The notification is performed by looping across the reference table and calling the common notification methodnotifydefined in the interface.
- A string table is included for demonstration and display purposes, being populated throughout method calls to visualize the method call flow.
- Multiple local classes act as observers, performing simple calculations.
- When the observed object calls the
notifymethod, it informs the observer about the state change, passes values, and triggers a calculation by invoking thecalculatemethod. - The global class represents the user, creating objects and registering and unregistering observers. The string table is output to visualize the method call flow.
- Defines the
- Example 2
- Provides a simpler example and setup, where the class being observed defines events.
- An interface implemented by all observers defines an event handler.
- Observers are notified through a
RAISE EVENTstatement, provided that event handlers are registered withSET HANDLERstatements, which triggers the calling of event handlers. When they are not registered, the event handlers are not called. - Like in example 1, a string table logs the method call flow, which is output.
- The user, represented by the global class implementation, manages object creation, and registration and unregistration using
SET HANDLERstatements. When the observed object'sset_textmethod is called, the event is raised and handled accordingly by event handlers in the registered observers.
- Example 1
- Global class:
| Class include | Code |
|
Global class |
|
|
CCIMP include (Local Types tab in ADT) |
|
Prototype
- The prototype design pattern suggests that you can clone objects to create copies of existing objects of classes.
- Cloning objects lets you preserve, reuse, and enhance an object's current state without altering the original prototype.
- This is especially useful when you want to delegate the task of object creation, particulary when the creation involves complex steps, instead of doing it manually. In cases where you need similar or the same objects, the prototype pattern simplifies and automates object creation, ensuring a consistent approach with the prototype providing a predefined state.
- Reusing and enhancing prototypes allows you to create variants of original objects. This pattern offers an alternative to complex inheritance trees, with object variants created through subclasses.
- In the pattern, prototype objects handle their own cloning. This can be done by providing a shared interface for all clonable objects. The interface may include a clone method. This clone method can be implemented to copy the entire state of the current object, including private attributes, or exclude attributes that should not be cloned. Instead of manually cloning objects to achieve a specific initial state, the prototype takes over the cloning, allowing users to easily retrieve the objects.
🟢 Click to expand for more information and example code
Example notes:
- The simplified example illustrates the prototype design pattern with the following declarations and implementations:
- Global class:
- Implements the
if_oo_adt_classruninterface and calls methods from local classes. - Serves as a vehicle for demonstrating the design pattern. The declarations and implementations in the
CCIMPare relevant for the for conceptual considerations. - The global class represents the user who creates objects of prototype classes and requires clones of these classes.
- Implements the
- CCIMP include (Local Types tab in ADT):
- Interface
lif_prototype- Serves as the common interface for all prototype classes, enforcing the implementation of the
clonemethod. - This method allows the creation of an clone of an object of a class that implements this interface.
- It returns a new reference, representing the object clone.
- Serves as the common interface for all prototype classes, enforcing the implementation of the
- Class
lcl_prototype_1- This demo class implements the
lif_prototypeinterface that allows users to retrieve a clone of an object from the class. - Contains various class attributes and methods. Methods such as
do_somethingmodify the value of class attributes. - The constructor (or any other part of the class that modifies the object's state) assumes there may be complex operations involved. However, this simplified example only assigns values to class attributes to demonstrate object cloning and enhancement.
- The clone method
lif_prototype~clonecreates a clone of the current object by instantiating a new class instance with theNEWoperator and copying the class attribute values. This example selectively clones certain attributes, showcasing a predefined state for the cloned object.
- This demo class implements the
- Class
lcl_prototype_2- This class is structured similarly to
lcl_prototype_1, but it features different attributes and behaviors.
- This class is structured similarly to
- Interface
- Global class:
- The class execution includes the following:
- The global class contains several method calls and class attribute accesses, demonstrating the prototype pattern. Both local classes,
lcl_prototype_1andlcl_prototype_2, are included. - Steps 1 and 4: Create objects of the prototype classes. Methods are called and class attributes are accessed to show changes in attribute values, resulting in specific object states.
- Steps 2 and 5: Clone objects using the
clonemethod, which creates copies of existing objects. The assumption is that the user needs a copy of the object to reuse a specific state instead of creating a new object from scratch. - Steps 3 and 6: Enhance the cloned objects. This involves casting to the prototype class to access its attributes and methods. The assumption is to create a variant of the object by modifying its attributes while using the original state as a foundation. Working with the variant involves a separate object, distinct from the original. A demo attribute shows that the clone method can be implemented to allow selective cloning, excluding certain attribute values.
- The example outputs various class attributes to illustrate both the original object states and the enhanced clone object states.
- The global class contains several method calls and class attribute accesses, demonstrating the prototype pattern. Both local classes,
| Class include | Code |
|
Global class |
|
|
CCIMP include (Local Types tab in ADT) |
|
Proxy
- The proxy design pattern proposes that an object of a class serves as a surrogate for another object.
- This means users interact with objects of a proxy class instead of interacting directly with an object of the original class.
- In this way, the proxy class can delegate and forward access to the original class as if users were working directly with it.
- Among the use cases for this design pattern is access control. A proxy class manages and restricts access to objects of another class, particularly for sensitive operations that only authorized users should perform. This setup prevents unauthorized access to the original class, ensuring that its methods cannot be called through the proxy. Additionally, you can enhance the proxy class with features like a logging mechanism that the original class does not provide, without modifying the original class itself. Other potential use cases include proxy classes that serve as surrogates for objects located in remote destinations, allowing interaction with remote objects through these proxies.
🟢 Click to expand for more information and example code
Example notes:
- The simplified example illustrates an access control context where only authorized users can retrieve data via a proxy class.
- This example demonstrates the proxy design pattern with the following declarations and implementations:
- Global class:
- Implements the
if_oo_adt_classruninterface, so the class can be run using F9 in ADT. - Serves as a vehicle for showcasing the design pattern. The declarations and implementations in the CCIMP include are relevant for conceptual considerations.
- The implementation includes multiple access attempts, i.e. method calls, through the proxy class. If authorized, the calls are delegated to the original class. It includes both successful and discarded calls, and finally, it retrieves and displays the content of a log table.
- Implements the
- CCIMP include (Local Types tab in ADT):
- Interface
lif_user_info: Defines the common interface for both the original and proxy classes. - Class
lcl_user_info: Represents the original class that implements the interface and contains the logic for retrieving user information. - Class
lcl_proxy:- Represents the proxy class.
- Implements the interface to serve as a surrogate of the original class. So, users interact with this class instead of the the original class.
- The class maintains a reference to the original class and delegates requests to it. The logic can only be executed, and data can only be retrieved if authentication validation succeeds. The example uses hardcoded demo values for usernames and passwords. If validation fails, an object of the original class cannot be created, an exception is raised (local exception class
lcx_error). - The original class,
lcl_user_info, is declared withCREATE PRIVATEto prevent external object creation. Only the class itself and its friends can instantiate objects, which is why the proxy class is declared as a friend of the class. - The proxy class includes additional functionality by implementing a simple logging mechanism. In this example, logging is achieved through entries in an internal table, which is a static component to track all access attempts from the internal session. Logging to a database table for permanent storage may be an option.
- Interface
- Global class:
| Class include | Code |
|
Global class |
|
|
CCIMP include (Local Types tab in ADT) |
|
Singleton
- Used to restrict external users from instantiating a class.
- This is typically achieved by using the
CREATE PRIVATEaddition in a class definition, preventing object creation outside the class. A factory method, often a static method, then provides users with an instance of the class. The singleton pattern ensures only one instance per class within an internal session. - Example of a predefined ABAP class:
CL_IXML_CORE.
🟢 Click to expand for more information and example code
Example notes:
- This example demonstrates the singleton design pattern with the following declarations and implementations:
- Global class:
- Implements the
if_oo_adt_classruninterface and calls methods from a local class. - Acts as a consumer of an API (a local class) defined in the CCIMP include.
- Implements the
- CCIMP include (Local Types tab in ADT):
- Contains the local class
lcl_singleton, which provides the static factory methodget_objthat supplies consumers with an instance (a single instance in this case) of thelcl_singletonclass. - Specifies the
CREATE PRIVATEaddition to prevent instance creation outside the class. - The
get_objmethod checks for an existing instance of thelcl_singletonclass. If it exists, it returns that instance; otherwise, it creates and returns a new instance. A private static attribute stores the reference to this instance. - To demonstrate the singleton pattern, an internal table, which represents a log table, is filled when calling the
add_logmethod and providing some text. The content of the table is returned when the consumer (the example global class) calls theget_logmethod. Theget_objis called several times, i.e. multiple object reference variables were assigned the reference to the single instance of the class. Yet, the table content returned at the end of the example shows the same content for all method calls via different object reference variables. It shows consistent data across object reference variables (i.e. the table has not been filled anew etc.). This way, a consistent logging is ensured within an internal session, throughout class execution. - Furthermore, the class implements both static and instance constructors. These constructors log time stamps and set static and instance attributes. The table that stores object reference variable names and time stamp values (created in the global class) and that is output to the console shows that the time stamps have not changed throughout the class execution, especially the time stamp set when calling the instance constructor, which is indeed called only once.
- Contains the local class
| Class include | Code |
|
Global class |
|
|
CCIMP include (Local Types tab in ADT) |
|
State
- The state of an object can be considered a specific situation that affects its behavior. So, an object's behavior depends on its current state.
- The state design pattern allows an object of a class to have different states, enabling it to change its behavior dynamically during runtime based on its current state.
- A potential setup, illustrated in the following example, may look like this:
- A class is defined whose objects' behavior changes. This class may manage the current state by storing a reference to the current state object as an attribute. Based on the current state, it forwards state-specific behavior calls to this object.
- An interface is defined that declares methods whose concrete implementations represent behaviors related to different states.
- Concrete state classes implement the interface, including code for the distinct behaviors for each state in the interface methods.
- The actual state transitions, which enable the execution of different functionalities, may be implemented in various ways. The following example includes two approaches:
- Delegating the task to the client, which is the global class in the example
- Implementing state transitions within the class whose objects' behavior changes based on external input
- The design pattern can be useful when you want to organize state-specific functionality in dedicated classes, enhancing maintenance and extensibility while reducing class complexity, aimed at not overcrowding classes with extensive code. It can also help avoid complex and repeated conditional logic.
🟢 Click to expand for more information and example code
Example notes:
- The example demonstrates the state design pattern through the following declarations and implementations. Note that this example is oversimplified, and multiple class setup strategies may apply. It uses a mathematical context to allow objects of the
lcl_mathandlcl_math_condclasses to perform mathematical operations. The object's behavior changes dynamically based on its current state or a condition. - Global class:
- Implements the
if_oo_adt_classruninterface and calls methods from local classes. - Serves as the client for demonstrating the design pattern. The declarations and implementations in the CCIMP include are relevant for conceptual considerations.
- Implements the
- CCIMP include (Local Types tab in ADT):
- Interface
lif_calc: Defines the shared interface for concrete state classes, including thecalculatemethod for performing arithmetic operations. Each state class implements this method differently. - Concrete state classes
lcl_state_addition,lcl_state_subtraction, andlcl_state_multiplication: These classes implement thelif_calcinterface and perform specific and simple calculations. lcl_math:- Represents the class whose objects' behavior changes.
- Declares private attributes, including the
state_refreference variable that holds the current object state, referencing an object that implementslif_calc. - The
set_statemethod updates the current state instate_ref. To work with the latest calculation result, the resulting values are assigned accordingly. - The
executemethod calls thecalculatemethod of the current state (if the reference variable is bound) and returns the result.
- The
lcl_math_condclass is similar tolcl_math, but it handles state transitions through conditional logic, and includes the object creation in the class itself.
- Interface
| Class include | Code |
|
Global class |
|
|
CCIMP include (Local Types tab in ADT) |
|
Strategy
- Assume you have a class and want to incorporate a set of related functionalities. Instead of overcrowding this class with multiple methods, the strategy design pattern allows you to put these functionalities in separate classes. Users can flexibly select and execute the functionality at runtime without modifying the base class code.
- To implement this, a strategy design pattern setup might look like this:
- A strategy interface defines a method that all concrete strategy classes must implement.
- Concrete strategy classes implement the strategy interface and provide specific functionalities.
- A base class maintains a reference to the strategy. Based on the strategy chosen at runtime, it delegates the execution of functionality accordingly, allowing users to interchange strategies seamlessly.
- This pattern enables flexibility in extending and maintaining functionality without requiring code modifications in the base class. It allows for the addition of more concrete strategy classes to enhance capabilities easily.
🟢 Click to expand for more information and example code
Example notes:
- The example code includes two examples demonstrating the strategy design pattern with the following declarations and implementations:
- Global class:
- Implements the
if_oo_adt_classruninterface and calls methods from local classes. - Serves as a vehicle for demonstrating the design pattern. The declarations and implementations in the
CCIMPare relevant for the for conceptual considerations.
- Implements the
- CCIMP include (Local Types tab in ADT):
- The example uses a simplified calculator context (simplified also in the sense, for example, that no proper error handling is implemented). The calculator performs various arithmetic operations, representing familiar functionalities. These operations are delegated to specific strategy classes.
- Two example setups are demonstrated: using an interface and an abstract class.
- Example setup with an interface:
- Interface
lif_calculation: Defines the shared interface for concrete strategy classes, including thecalculatemethod. - Concrete strategy classes (
lcl_addition,lcl_subtraction,lcl_multiplication,lcl_division) implement thelif_calculationinterface and provide specific calculation strategies. - The base class
lcl_calculatorhas two methods:set_strategy_ref: Sets a reference to a strategy. It expects an object that implements thelif_calculationinterface.calculate: Delegates execution to the concrete strategy class. This setup allows for flexible delegation at runtime to any concrete strategy class that implements the shared interface.
- Interface
- Example setup with an abstract class:
- Abstract class
lcl_strategy_abstract: Similar to thelif_calculationinterface, it defines the shared interface that concrete strategy classes use. - Concrete strategy classes inherit from the abstract superclass and implement the abstract method.
- Abstract class
- The user, represented by the global class implementation, manages object creation, sets the strategy by calling the
set_strategy_refmethod, and passes appropriate objects. When thecalculatemethod is called, the corresponding functionality is executed based on the previously passed object.
- Global class:
| Class include | Code |
|
Global class |
|
|
CCIMP include (Local Types tab in ADT) |
|
Template Method
- The template method design pattern suggests that a template method, typically included in an abstract superclass, defines and outlines the sequence of steps, i.e. method calls, needed to perform certain tasks.
- Abstract methods can be defined in the abstract superclass, and calls to these methods can be included in the template method. Subclasses that inherit from the abstract superclass provide the concrete implementations of these abstract methods.
- The template method implementation may also include calls to non-abstract methods from the abstract superclass, which are implemented in the superclass. This ensures that all users share the same functionality.
- Thus, the template method pattern allows to organize method calls into a common structure. It enables the standard functionality provided by the superclass while allowing subclasses to flexibly modify specific methods for custom implementations. This can be helpful for use cases that involve similar tasks and require the same sequence of method calls, while still allowing for customized functionality.
- This approach can simplify code maintenance and promote encapsulation. For example, if you need to modify the standard functionality, you only have to adapt the code in the superclass. Similarly, if a specific subclass requires code changes, you can update the abstract method implementations without altering the underlying base structure.
- A potential, high-level class design, which is also used in the example, might look as follows:
- An abstract class defines a common interface for its subclasses.
- The abstract class may include:
- Abstract methods for subclasses to implement.
- Non-abstract methods, providing standard functionality for all users in the template method. This approach avoids code duplication.
- A template method to orchestrate a specific sequence of method calls. These method calls may include both abstract methods (implemented in subclasses) and non-abstract methods (implemented in the abstract superclass).
- Subclasses, representing concrete classes, inherit from the abstract superclass and must implement the abstract methods.
🟢 Click to expand for more information and example code
Example notes:
- The following example demonstrates a consistent flow of steps defined in a template method that is common to variants. It uses an XML and JSON parsing context. The steps in the template method are as follows:
- The XML and JSON data is provided as a string and converted to an xstring.
- The data is then parsed.
- The parsed data is output.
- The example illustrates the template method design pattern with the following declarations and implementations:
- Global class:
- Implements the
if_oo_adt_classruninterface and calls methods from local classes. - Serves as a vehicle for demonstrating the design pattern. The declarations and implementations in the CCIMP include are relevant for the conceptual considerations.
- Represents the user who expects XML and JSON data to be parsed.
- Implements the
- CCIMP include (Local Types tab in ADT):
- Abstract class
lcl_data_parser- Serves as the common interface for all subclasses that inherit from this class.
- Includes protected non-abstract methods (
convert_to_xstring,output_data) that are implemented in the superclass. - Contains the abstract method
parse, requiring subclasses to provide their own implementation. - Includes the public, final template method
template_method, which specifies the sequence of method calls mentioned earlier.
- Multiple concrete subclasses inherit from the abstract superclass, each providing custom functionality in the
parsemethod:lcl_xml_parser_ixml: Reads XML data using the iXML library.lcl_xml_parser_sxml: Reads XML data using the sXML library.lcl_json_parser_sxml: Reads JSON data using the sXML library. This implementation is similar tolcl_xml_parser_sxmland is only included to add more concrete classes.lcl_json_parser_ui2cljson: Reads JSON data using the/ui2/cl_jsonclass and thegeneratemethod, which creates an ABAP data object from JSON. It is also only included to add more demo concrete classes. Instead of the typical usage of the class to include JSON data in known (in this case, unknown) data objects, the implementation here explores the created data and dynamic programming techniques. It is meant for exploration purposes and neither represents best practices nor claims to cover all JSON variants (beyond the demo JSON data covered in the global class).
- The simplified parsing returns values in a string table for display.
- Note that the example does not include proper error handling.
- Abstract class
- Global class:
| Class include | Code |
|
Global class |
|
|
CCIMP include (Local Types tab in ADT) |
|
Visitor
- The visitor design pattern allows you to add functionality to objects flexibly without altering the underlying class setup. This pattern enables you to perform various operations on the same object structure without bloating the class definitions.
- The demo example below illustrates this pattern: an object holds data (in this case, database table entries are retrieved and stored in an internal table), while different visitors can process that data through various operations, such as XML or JSON conversion. The class setup eliminates the need to add methods for each new functionality in the underlying class. In the future, if more functionalities need to be added, this approach prevents class bloating that would occur from continually adding methods. The visitor pattern offers the flexibility to extend functionality as needed, allowing users to integrate new capabilities into the object without overcrowding the original class.
🟢 Click to expand for more information and example code
Example notes:
- The example code includes two examples demonstrating the visitor design pattern with the following declarations and implementations:
- Global class:
- Implements the
if_oo_adt_classruninterface and calls methods from local classes. - Serves as a vehicle for demonstrating the design pattern. The declarations and implementations in the
CCIMPare relevant for the for conceptual considerations.
- Implements the
- CCIMP include (Local Types tab in ADT):
lif_visitor:- Defines the shared interface for visitor classes.
- A method and attributes are defined that provide a way for different visitor implementations to interact with instances of the
lcl_visitedclass. - The
visitmethod expects an instance of the thelcl_visitedclass and can be processed further by the visitor class for whatever purpose. - The instance attributes
result_xstringandresult_stringare included to store results of the processing in thevisitmethod.
lcl_visited- Represents the class to be visited
- It encapsulates the logic for loading and storing entries of a data source, and and allows visitor classes to operate on it.
- Methods:
set_itab_content_ref: Takes a the name of a data source with an optional number of entries to perform aSELECTstatement, storing results in the data reference variabletable_data_ref, a private attribute.get_itab_content_ref: Returns a reference to the internal table data.receive_visitor: Accepts a visitor object and calls itsvisitmethod, passing an object of the class itself (me) as an argument.
- Concrete visitor classes
- Each visitor class implements the
lif_visitorinterface to process the table data oflcl_visiteddifferently. In doing so, the setup allows for extending the capabilities oflcl_visitedwithout changing the underlying code. lcl_visitor_xml: Implements the conversion of table data into XML format using aCALL TRANSFORMATIONstatement.lcl_visitor_json: Implements the serialization of table data into JSON format using/ui2/cl_json.lcl_visitor_count: Determines the number of lines in the table.lcl_visitor_cont_str: Converts the table content into string format.
- Each visitor class implements the
- The user, represented by the global class implementation, manages object creation, calls the
receive_visitormethod and passes appropriate objects. Accessing thelif_visitor~result_xstringandlif_visitor~result_stringillustrates the results of the differently implemented operations.
- Global class:
| Class include | Code |
|
Global class |
|
|
CCIMP include (Local Types tab in ADT) |
|