As stated in that blog, most singletons are a variation on a theme, and the ABAP example code provided in that blog concentrated on the use of only one of those variations. In this blog we explore some other variations of defining and using a singleton object.
To recap, Singleton is an object-oriented design pattern guaranteeing only a single instance of a class will exist during program execution. It is one of the 23 object-oriented design patterns covered in the book Design Patterns: Elements of Reusable Object-Oriented Software (Gamma, Helms, Johnson, Vlissides). This book, first published in 1995, has become embraced by many object-oriented scholars as the definitive compendium of design patterns for use in computer programming. Its four authors have become known in the industry as the “Gang of Four” and many now refer to the book simply by the acronym for this moniker: GoF.
According to GoF, the intent behind the Singleton design pattern is the following:
Ensure a class only has one instance and provide a global point of access to it.
The first part of this intent suggests we need to be able to control the instantiation of the object to insure only one object is ever created, whereas the second part suggests we need to be able to provide access to it once the object has been created.
Many object-oriented languages provide the capabilities to restrict object creation to only certain specified entities. One of these capabilities enables restricting object creation to the class itself. This is known as private instantiation. For classes written in ABAP, private instantiation is indicated by including the “create private” qualifier on the class definition statement, as in:
class some_class_name definition ... create private.
Indeed, with ABAP there always will be a “create” option associated with a class definition. The word that may follow “create” is “public”, “protected” or “private”. When not explicitly indicated otherwise, option “create public” is in effect for a class and denotes that any entity may create an instance of the class.
Having the “create private” qualifier on the class definition statement means that no other entities may create instances of this class – only this class can create instances of this class. This concept may be hard to grasp at first and many new to this idea may find themselves asking “How can any instances of such a class be created if only the class itself can create those instances?” This conundrum is easily resolved by defining a class with both static and instance members (attributes and behaviors). The static members facilitate creating an instance and providing access to it. Static members are available at execution time even when no instances of the class have yet been created. Accordingly, a class defined with private instantiation can have public static methods enabling a caller to request the class to create an instance and provide the caller with the instance reference. Whereas private instantiation restricts object creation to the class itself, it does not prevent the class from creating multiple instances.
To insure only a single instance of the class is created, a static attribute can be defined for the class to hold a reference to what will become the one and only instance of the class. The static methods used to provide access to instances of the class would check whether the static attribute to hold a reference to this sole instance is bound (contains a reference to an active object) or is not bound (is empty or contains a reference to an inactive object). When not bound, an instance of the class would be created and its reference placed into the static attribute holding the reference to the sole instance. This technique is known as lazy initialization, in this case delaying the instantiation of the sole instance until the first request for it. From that point forward a check by the static accessor methods of the static reference attribute would provide a bound reference to an instance of the class, so no other instantiations of the class would be necessary, meaning that the first instantiation of the class with its reference placed into the static attribute would be the only instantiation, resulting in one and only one instance of the class. For a singleton, the static members of the class would facilitate access to its sole object and the class would have only as many static members defined for it as would be required to achieve this.
So far we have seen that to guarantee one and only one instance of a class we need a combination of the following, the three criteria for defining a singleton:
◈ Class is defined with private instantiation.
◈ Class is defined with static attribute defined as reference to an object of the very same class.
◈ Class is defined with static method to provide access to the sole instance of the class.
If we define all variations of our singleton classes with the create private qualifier, then the following variations of object instantiation are possible:
1. static constructor method instantiates sole object, placing its reference into public static attribute.
2. static constructor method instantiates sole object, placing its reference into private static attribute.
3. static accessor method instantiates sole object using lazy initialization, placing its reference into private static attribute.
leaving the following variations of object access as possibilities:
a. sole object is accessed directly through public static attribute holding its reference.
b. reference to sole object is provided as exporting parameter of static instance accessor method.
c. reference to sole object is provided as returning parameter of static instance accessor method.
Notice that for each of the instantiation possibilities noted above it is a static method creating the sole instance and a static attribute holding the corresponding reference to the sole instance. Similarly, for each of the object access possibilities noted above it is a static attribute or a static instance accessor method providing the reference to the sole instance. Accordingly, all members of the class providing access to the sole instance are static members.
The only practical combinations of these variations on object instantiation and object access are the following:
1a – Caller accesses sole instance directly through public static attribute of class.
2b – Caller must provide its own instance reference variable to accept reference to sole instance.
2c – Caller may dispense with providing its own instance reference variable to accept reference to sole instance when using “chained method call” to access it.
3b – Same as 2b with lazy initialization.
3c – Same as 2c with lazy initialization.
Let’s explore each combination.
The following ABAP source code will be used as a starting point for exploring the singleton variations and represents the general design of a sample program which is to create a report containing a list of unique registration numbers based on a count provided by the user on an initial selection screen of registration numbers to be included in the list.
report.
class registration_number_manager definition
abstract
final.
public section.
types : registration_number_type
type n length 05.
class-methods: get_next_registration_number
exporting
next_registration_number
type registration_number_manager=>registration_number_type.
private section.
class-data : next_registration_number
type registration_number_manager=>registration_number_type
value 1000.
endclass.
class registration_number_manager implementation.
method get_next_registration_number.
add 01 to registration_number_manager=>next_registration_number.
next_registration_number = registration_number_manager=>next_registration_number.
endmethod.
endclass.
class process_driver definition
abstract
final.
public section.
types : iteration_type type n length 02.
class-methods: drive_process
importing
iterations
type process_driver=>iteration_type.
endclass.
class process_driver implementation.
method drive_process.
data : registration_number
type registration_number_manager=>registration_number_type.
do iterations times.
call method registration_number_manager=>get_next_registration_number
importing
next_registration_number
= registration_number.
write: / 'Iteration :'
, sy-index
, / 'Registration number:'
, registration_number
.
enddo.
endmethod.
endclass.
parameters : iterate type process_driver=>iteration_type.
start-of-selection.
call method process_driver=>drive_process
exporting
iterations = iterate.
In this case all processing is contained in two static classes: class “registration_number_manager” is accessed by class “process_driver”, and class “process_driver” is accessed by the start-of-selection classic ABAP event block. Open your favorite editor, make a copy of the code above and execute it to confirm that it works properly and will produce a list of unique registration numbers based on a number you provide on the initial selection screen. Then follow along as we transform the static classes of this program to use different variations of the Singleton design pattern. Also, I recommend retaining each completed variation as its own separate program since the source code for some variations explored here will start by using the completed source code of a previous variation.
Caller accesses sole instance directly through public static attribute of class.
This variation of singleton is the same model that had been used with the singletons created in Part 6 of the 6-part blog series Getting Comfortable using the Object-Oriented design model with ABAP.
Since there are two static classes in this program, we will transform each of them into corresponding singleton classes. We’ll start with static class “registration_number_manager”. Apply the following changes to transform it from a static class into a singleton class using variation 1a:
◈ Remove the qualifier “abstract” from its class definition.
◈ Include the qualifier “create private” in its class definition, following the qualifier “final”.
◈ Include the following two statements in the public section preceding the “class-methods” statement:
class-data : singleton type ref to registration_number_manager read-only.
class-methods: class_constructor.
◈ Change the definition of public method “get_next_registration_number” from static method to instance method by replacing “class-methods” with “methods”.
◈ Change the definition of private attribute “next_registration_number” from a static attribute to an instance attribute by replacing “class-data” with “data”.
◈ Include the following lines after the class implementation statement:
method class_constructor.
create object registration_number_manager=>singleton.
endmethod.
◈ In method “get_next_registration_number”, replace both occurrences of “registration_number_manager=>” with “me->”.
Afterward class “registration_number_manager” should look like this:
class registration_number_manager definition
final
create private.
public section.
types : registration_number_type
type n length 05.
class-data : singleton type ref to registration_number_manager read-only.
class-methods: class_constructor.
methods : get_next_registration_number
exporting
next_registration_number
type registration_number_manager=>registration_number_type.
private section.
data : next_registration_number
type registration_number_manager=>registration_number_type
value 1000.
endclass.
class registration_number_manager implementation.
method class_constructor.
create object registration_number_manager=>singleton.
endmethod.
method get_next_registration_number.
add 01 to me->next_registration_number.
next_registration_number = me->next_registration_number.
endmethod.
endclass.
A syntax check at this point should fail on the “call method” statement in method “drive_process” of class “process_driver”. Resolve this by changing the statement
call method registration_number_manager=>get_next_registration_number
to
call method registration_number_manager=>singleton->get_next_registration_number
Afterward a syntax check will pass. Executing the program at this point should prove that it works the same as it had prior to applying these changes.
Next, apply the following changes to transform “process_driver” from a static class into a singleton class using variation 1a:
◈ Remove the qualifier “abstract” from its class definition.
◈ Include the qualifier “create private” in its class definition, following the qualifier “final”.
◈ Include the following two statements in the public section preceding the “class-methods” statement:
class-data : singleton type ref to process_driver read-only.
class-methods: class_constructor.
◈ Change the definition of public method “drive_process” from static method to instance method by replacing “class-methods” with “methods”.
◈ Include the following lines after the class implementation statement:
method class_constructor.
create object process_driver=>singleton.
endmethod.
Afterward class “process_driver” should look like this:
class process_driver definition
final
create private.
public section.
types : iteration_type type n length 02.
class-data : singleton type ref to process_driver read-only.
class-methods: class_constructor.
methods : drive_process
importing
iterations
type process_driver=>iteration_type.
endclass.
class process_driver implementation.
method class_constructor.
create object process_driver=>singleton.
endmethod.
method drive_process.
data : registration_number
type registration_number_manager=>registration_number_type.
do iterations times.
call method registration_number_manager=>singleton->get_next_registration_number
importing
next_registration_number
= registration_number.
write: / 'Iteration :'
, sy-index
, / 'Registration number:'
, registration_number
.
enddo.
endmethod.
endclass.
A syntax check at this point should fail on the “call method” statement in the start-of-selection classic ABAP event block. Resolve this by changing the statement
call method process_driver=>drive_process
to
call method process_driver=>singleton->drive_process
Afterward a syntax check will pass. Executing the program at this point should prove that it works the same as it had prior to applying these changes.
Notice that method “drive_process” of class “process_driver” is invoked only from within the start-of-selection classic ABAP event block. Using this variation of the singleton enables the non-object-oriented statements of the program to remain devoid of defining a global variable to provide access to the singleton instance of class “process_driver”.
If you find yourself confused by how the statement
call method process_driver=>singleton->drive_process
could possibly work, refer to the explanation provided in this blog.
Here is how singleton variation 1a complies with the three criteria for defining a singleton:
◈ Class is defined with private instantiation.
Yes, via the “create private” defined on the class definition statement.
◈ Class is defined with static attribute defined as reference to an object of the very same class.
Yes, the static attribute is defined with public visibility and is assigned the “read-only” qualifier so that external entities can access it but cannot change its value.
◈ Class is defined with static method to provide access to the sole instance of the class.
Yes, the static constructor method creates the singleton instance, placing its reference into the public static attribute implicitly available to external entities.
Caller must provide its own instance reference variable to accept reference to sole instance.
Here we are going to apply this singleton variation only to class “registration_number_manager”, but the same process can be applied to class “process_driver”. First, make a new copy using the sample program from above. Then apply the following changes to class “registration_number_manager” to transform it from a static class into a singleton class using variation 2b:
◈ Remove the qualifier “abstract” from its class definition.
◈ Include the qualifier “create private” in its class definition, following the qualifier “final”.
◈ Include the following statements in the pubic section preceding the “class-methods” statement:
class-methods: class_constructor.
class-methods: get_instance
exporting
instance
type ref to registration_number_manager.
◈ Change the definition of public method “get_next_registration_number” from static method to instance method by replacing “class-methods” with “methods”.
◈ Include the following statement in the private section preceding the “class-data” statement:
class-data : singleton type ref to registration_number_manager.
◈ Change the definition of private attribute “next_registration_number” from a static attribute to an instance attribute by replacing “class-data” with “data”.
◈ Include the following lines after the class implementation statement:
method class_constructor.
create object registration_number_manager=>singleton.
endmethod.
◈ In method “get_next_registration_number”, replace both occurrences of “registration_number_manager=>” with “me->”.
◈ Include the following lines after the last “endmethod” statement:
method get_instance.
instance = registration_number_manager=>singleton.
endmethod.
Afterward class “registration_number_manager” should look like this:
class registration_number_manager definition
final
create private.
public section.
types : registration_number_type
type n length 05.
class-methods: class_constructor.
class-methods: get_instance
exporting
instance
type ref to registration_number_manager.
methods : get_next_registration_number
exporting
next_registration_number
type registration_number_manager=>registration_number_type.
private section.
class-data : singleton type ref to registration_number_manager.
data : next_registration_number
type registration_number_manager=>registration_number_type
value 1000.
endclass.
class registration_number_manager implementation.
method class_constructor.
create object registration_number_manager=>singleton.
endmethod.
method get_next_registration_number.
add 01 to me->next_registration_number.
next_registration_number = me->next_registration_number.
endmethod.
method get_instance.
instance = registration_number_manager=>singleton.
endmethod.
endclass.
A syntax check at this point should fail on the “call method” statement in method “drive_process” of class “process_driver”. Resolve this by placing the following statement preceding the “do iterations times” statement:
data : registration_number_manager
type ref to registration_number_manager.
and by replacing the single statement:
call method registration_number_manager=>get_next_registration_number
importing
next_registration_number
= registration_number.
with the following two statements:
call method registration_number_manager=>get_instance
importing
instance = registration_number_manager.
call method registration_number_manager->get_next_registration_number
importing
next_registration_number
= registration_number.
Afterward a syntax check will pass. Executing the program at this point should prove that it works the same as it had prior to applying these changes.
Notice in this case that the user of the singleton class first needs to call a public static method of the singleton class to fetch a reference to the singleton object, providing its own reference variable for this activity, and then invoking the singleton object through that same reference variable.
Here is how singleton variation 2b complies with the three criteria for defining a singleton:
◈ Class is defined with private instantiation.
Yes, via the “create private” defined on the class definition statement.
◈ Class is defined with static attribute defined as reference to an object of the very same class.
Yes, the static attribute is defined with private visibility making it inaccessible to external entities.
◈ Class is defined with static method to provide access to the sole instance of the class.
Yes, the static constructor method creates the singleton instance, placing its reference into the private static attribute unavailable to external entities, and public static accessor method “get_instance” makes this reference available to external entities via exporting parameter.
Caller may dispense with providing its own instance reference variable to accept reference to sole instance when using “chained method call” to access it.
Here again we are going to apply this singleton variation only to class “registration_number_manager”, but the same process can be applied to class “process_driver”. First, make a new copy using the variation 2b program just completed. Then apply the following changes to class “registration_number_manager” to transform it from a singleton class using variation 2b to a singleton class using variation 2c:
◈ Change the definition of public static method “get_instance” by replacing
exporting
instance
with
returning
value(instance)
Afterward class “registration_number_manager” should look like this:
class registration_number_manager definition
final
create private.
public section.
types : registration_number_type
type n length 05.
class-methods: class_constructor.
class-methods: get_instance
returning
value(instance)
type ref to registration_number_manager.
methods : get_next_registration_number
exporting
next_registration_number
type registration_number_manager=>registration_number_type.
private section.
class-data : singleton type ref to registration_number_manager.
data : next_registration_number
type registration_number_manager=>registration_number_type
value 1000.
endclass.
class registration_number_manager implementation.
method class_constructor.
create object registration_number_manager=>singleton.
endmethod.
method get_next_registration_number.
add 01 to me->next_registration_number.
next_registration_number = me->next_registration_number.
endmethod.
method get_instance.
instance = registration_number_manager=>singleton.
endmethod.
endclass.
A syntax check at this point should fail on the “call method get_instance” statement in method “drive_process” of class “process_driver”. Resolve this by applying the following change to method “drive_process”:
Replace the word “importing” in the statement
call method registration_number_manager=>get_instance
importing
instance = registration_number_manager.
with the word “receiving”, as in:
call method registration_number_manager=>get_instance
receiving
instance = registration_number_manager.
Afterward a syntax check will pass. Executing the program at this point should prove that it works the same as it had prior to applying these changes.
Whereas the change we applied works, it does not illustrate the most significant reason for having changed the signature of method get_instance of class registration_number_manager to return a value rather than export one. When the signature of get_instance is defined to return the instance, then we can use this method in a chained method call and eliminate the local variable defined to receive the reference to the instance.
To see how this works, do the following:
◈ In method “drive_process” of class “process_driver”, remove the definition for local variable “registration_number_manager”.
A syntax check at this point should fail on the “call method get_instance” statement in this method. Resolve this by applying the following changes to method “drive_process”:
◈ Replace the two statements
call method registration_number_manager=>get_instance
receiving
instance = registration_number_manager.
call method registration_number_manager->get_next_registration_number
importing
next_registration_number
= registration_number.
with the single statement
call method registration_number_manager=>get_instance( )->get_next_registration_number(
importing
next_registration_number
= registration_number
).
Afterward a syntax check will pass. Executing the program at this point should prove that it works the same as it had prior to applying these changes.
Notice that with this variation of singleton the caller no longer is required to provide the local variable previously used to retrieve a reference to the singleton object.
Here is how singleton variation 2c complies with the three criteria for defining a singleton:
◈ Class is defined with private instantiation.
Yes, via the “create private” defined on the class definition statement.
◈ Class is defined with static attribute defined as reference to an object of the very same class.
Yes, the static attribute is defined with private visibility making it inaccessible to external entities.
◈ Class is defined with static method to provide access to the sole instance of the class.
Yes, the static constructor method creates the singleton instance, placing its reference into the private static attribute unavailable to external entities, and public static accessor method “get_instance” makes this reference available to external entities via returning parameter.
Caller must provide its own instance reference variable to accept reference to sole instance.
Again we are going to apply this singleton variation only to class “registration_number_manager”, but the same process can be applied to class “process_driver”. First, make a new copy using the variation 2b program just completed. Then apply the following changes to class “registration_number_manager” to transform it from a singleton class using variation 2b to a singleton class using variation 3b:
◈ Remove the “class_constructor” definition and implementation.
◈ Include the following three statements as the first statements of method “get_instance”:
if registration_number_manager=>singleton is not bound.
create object registration_number_manager=>singleton.
endif.
Afterward class “registration_number_manager” should look like this:
class registration_number_manager definition
final
create private.
public section.
types : registration_number_type
type n length 05.
class-methods: get_instance
exporting
instance
type ref to registration_number_manager.
methods : get_next_registration_number
exporting
next_registration_number
type registration_number_manager=>registration_number_type.
private section.
class-data : singleton type ref to registration_number_manager.
data : next_registration_number
type registration_number_manager=>registration_number_type
value 1000.
endclass.
class registration_number_manager implementation.
method get_next_registration_number.
add 01 to me->next_registration_number.
next_registration_number = me->next_registration_number.
endmethod.
method get_instance.
if registration_number_manager=>singleton is not bound.
create object registration_number_manager=>singleton.
endif.
instance = registration_number_manager=>singleton.
endmethod.
endclass.
A syntax check at this point should pass. Executing the program at this point should prove that it works the same as it had prior to applying these changes.
Here we are using what is known as lazy initialization. A singleton instance is not created until a caller explicitly invokes the static accessor method “get_instance”.
Here is how singleton variation 3b complies with the three criteria for defining a singleton:
◈ Class is defined with private instantiation.
Yes, via the “create private” defined on the class definition statement.
◈ Class is defined with static attribute defined as reference to an object of the very same class.
Yes, the static attribute is defined with private visibility making it inaccessible to external entities.
◈ Class is defined with static method to provide access to the sole instance of the class.
Yes, the public static accessor method “get_instance” makes this reference available to external entities via exporting parameter, using lazy initialization to create an instance if the reference is not yet bound.
Caller may dispense with providing its own instance reference variable to accept reference to sole instance when using “chained method call” to access it.
Again we are going to apply this singleton variation only to class “registration_number_manager”, but the same process can be applied to class “process_driver”. First, make a new copy using the variation 2c program just completed. Then apply the following changes to class “registration_number_manager” to transform it from a singleton class using variation 2c to a singleton class using variation 3c (indeed, these changes are identical the changes applied between variations 2b and 3b):
◈ Remove the “class_constructor” definition and implementation.
◈ Include the following three statements as the first statements of method “get_instance”:
if registration_number_manager=>singleton is not bound.
create object registration_number_manager=>singleton.
endif.
Afterward class “registration_number_manager” should look like this:
class registration_number_manager definition
final
create private.
public section.
types : registration_number_type
type n length 05.
class-methods: get_instance
returning
value(instance)
type ref to registration_number_manager.
methods : get_next_registration_number
exporting
next_registration_number
type registration_number_manager=>registration_number_type.
private section.
class-data : singleton type ref to registration_number_manager.
data : next_registration_number
type registration_number_manager=>registration_number_type
value 1000.
endclass.
class registration_number_manager implementation.
method get_next_registration_number.
add 01 to me->next_registration_number.
next_registration_number = me->next_registration_number.
endmethod.
method get_instance.
if registration_number_manager=>singleton is not bound.
create object registration_number_manager=>singleton.
endif.
instance = registration_number_manager=>singleton.
endmethod.
endclass.
A syntax check at this point should pass. Executing the program at this point should prove that it works the same as it had prior to applying these changes.
Here again we are using lazy initialization. A singleton instance is not created until a caller explicitly invokes the static accessor method get_instance.
Here is how singleton variation 3c complies with the three criteria for defining a singleton:
◈ Class is defined with private instantiation.
Yes, via the “create private” defined on the class definition statement.
◈ Class is defined with static attribute defined as reference to an object of the very same class.
Yes, the static attribute is defined with private visibility making it inaccessible to external entities.
◈ Class is defined with static method to provide access to the sole instance of the class.
Yes, the public static accessor method “get_instance” makes this reference available to external entities via returning parameter, using lazy initialization to create an instance if the reference is not yet bound.
To recap, the singleton variations presented here are these:
1a – Caller accesses sole instance directly through public static attribute of class.
2b – Caller must provide its own instance reference variable to accept reference to sole instance.
2c – Caller may dispense with providing its own instance reference variable to accept reference to sole instance when using “chained method call” to access it.
3b – Same as 2b with lazy initialization.
3c – Same as 2c with lazy initialization.
Variations 2b and 3b seem to be the least useful since the user of a singleton defined this way must provide its own receiver for the singleton instance when calling the public static method providing the instance (method “get_instance” in this case). This would be particularly egregious when invoking such a singleton from a classic ABAP event block since it would require the use of a global variable to receive the reference. Variations 2c and 3c also would accommodate this capability if the user chose to provide its own receiver, but these variations also provide the ability to use a chained method call to invoke the instance methods of the singleton, bypassing the necessity for the caller to provide its own receiver field to accept a reference to the singleton.
Variations 3b and 3c describe singletons using the lazy initialization technique to instantiate the sole object with the first call to the “get_instance” method. These variations have no static constructor creating the sole instance. By contrast, variations 2b and 2c do have static constructors creating the sole instance, static constructors that would be triggered upon entering a block of ABAP code containing an ABAP statement with a reference to a static member of the class, regardless whether that statement is actually executed. Accordingly, the lazy initialization used with singleton variations 3b and 3c would prevent a sole instance from being created unnecessarily if the execution path through the block of code results in the statement referencing the singleton being bypassed.
Variations 2c and 3c require two method invocations to reach an instance method of the sole object when using the chained method call technique as shown in the format below:
call method some_class=>get_instance( )->some_instance_method( ).
The first method call is the one to the static method “some_class=>get_instance”, which returns a reference to the sole instance, which is then used as the object on which the second, instance method call to “some_instance_method” is invoked. If there were to be repeated calls to the instance methods of the sole instance of class “some_class” defined as a variation 2c or 3c singleton, then the chained method call could be replaced with a single initial call to static method “some_class=>get_instance”, saving the instance reference it receives in a local variable and then using this local variable to invoke the repeated calls to the singleton’s instance methods, thus reducing the number of method calls necessary to access the instance methods of the sole instance.
To recap, Singleton is an object-oriented design pattern guaranteeing only a single instance of a class will exist during program execution. It is one of the 23 object-oriented design patterns covered in the book Design Patterns: Elements of Reusable Object-Oriented Software (Gamma, Helms, Johnson, Vlissides). This book, first published in 1995, has become embraced by many object-oriented scholars as the definitive compendium of design patterns for use in computer programming. Its four authors have become known in the industry as the “Gang of Four” and many now refer to the book simply by the acronym for this moniker: GoF.
Ensure a class only has one instance and provide a global point of access to it.
The first part of this intent suggests we need to be able to control the instantiation of the object to insure only one object is ever created, whereas the second part suggests we need to be able to provide access to it once the object has been created.
Private Instantiation
Many object-oriented languages provide the capabilities to restrict object creation to only certain specified entities. One of these capabilities enables restricting object creation to the class itself. This is known as private instantiation. For classes written in ABAP, private instantiation is indicated by including the “create private” qualifier on the class definition statement, as in:
class some_class_name definition ... create private.
Indeed, with ABAP there always will be a “create” option associated with a class definition. The word that may follow “create” is “public”, “protected” or “private”. When not explicitly indicated otherwise, option “create public” is in effect for a class and denotes that any entity may create an instance of the class.
Having the “create private” qualifier on the class definition statement means that no other entities may create instances of this class – only this class can create instances of this class. This concept may be hard to grasp at first and many new to this idea may find themselves asking “How can any instances of such a class be created if only the class itself can create those instances?” This conundrum is easily resolved by defining a class with both static and instance members (attributes and behaviors). The static members facilitate creating an instance and providing access to it. Static members are available at execution time even when no instances of the class have yet been created. Accordingly, a class defined with private instantiation can have public static methods enabling a caller to request the class to create an instance and provide the caller with the instance reference. Whereas private instantiation restricts object creation to the class itself, it does not prevent the class from creating multiple instances.
Singleton
To insure only a single instance of the class is created, a static attribute can be defined for the class to hold a reference to what will become the one and only instance of the class. The static methods used to provide access to instances of the class would check whether the static attribute to hold a reference to this sole instance is bound (contains a reference to an active object) or is not bound (is empty or contains a reference to an inactive object). When not bound, an instance of the class would be created and its reference placed into the static attribute holding the reference to the sole instance. This technique is known as lazy initialization, in this case delaying the instantiation of the sole instance until the first request for it. From that point forward a check by the static accessor methods of the static reference attribute would provide a bound reference to an instance of the class, so no other instantiations of the class would be necessary, meaning that the first instantiation of the class with its reference placed into the static attribute would be the only instantiation, resulting in one and only one instance of the class. For a singleton, the static members of the class would facilitate access to its sole object and the class would have only as many static members defined for it as would be required to achieve this.
Variations on a Singleton
So far we have seen that to guarantee one and only one instance of a class we need a combination of the following, the three criteria for defining a singleton:
◈ Class is defined with private instantiation.
◈ Class is defined with static attribute defined as reference to an object of the very same class.
◈ Class is defined with static method to provide access to the sole instance of the class.
If we define all variations of our singleton classes with the create private qualifier, then the following variations of object instantiation are possible:
1. static constructor method instantiates sole object, placing its reference into public static attribute.
2. static constructor method instantiates sole object, placing its reference into private static attribute.
3. static accessor method instantiates sole object using lazy initialization, placing its reference into private static attribute.
leaving the following variations of object access as possibilities:
a. sole object is accessed directly through public static attribute holding its reference.
b. reference to sole object is provided as exporting parameter of static instance accessor method.
c. reference to sole object is provided as returning parameter of static instance accessor method.
Notice that for each of the instantiation possibilities noted above it is a static method creating the sole instance and a static attribute holding the corresponding reference to the sole instance. Similarly, for each of the object access possibilities noted above it is a static attribute or a static instance accessor method providing the reference to the sole instance. Accordingly, all members of the class providing access to the sole instance are static members.
The only practical combinations of these variations on object instantiation and object access are the following:
1a – Caller accesses sole instance directly through public static attribute of class.
2b – Caller must provide its own instance reference variable to accept reference to sole instance.
2c – Caller may dispense with providing its own instance reference variable to accept reference to sole instance when using “chained method call” to access it.
3b – Same as 2b with lazy initialization.
3c – Same as 2c with lazy initialization.
Let’s explore each combination.
Sample program
The following ABAP source code will be used as a starting point for exploring the singleton variations and represents the general design of a sample program which is to create a report containing a list of unique registration numbers based on a count provided by the user on an initial selection screen of registration numbers to be included in the list.
report.
class registration_number_manager definition
abstract
final.
public section.
types : registration_number_type
type n length 05.
class-methods: get_next_registration_number
exporting
next_registration_number
type registration_number_manager=>registration_number_type.
private section.
class-data : next_registration_number
type registration_number_manager=>registration_number_type
value 1000.
endclass.
class registration_number_manager implementation.
method get_next_registration_number.
add 01 to registration_number_manager=>next_registration_number.
next_registration_number = registration_number_manager=>next_registration_number.
endmethod.
endclass.
class process_driver definition
abstract
final.
public section.
types : iteration_type type n length 02.
class-methods: drive_process
importing
iterations
type process_driver=>iteration_type.
endclass.
class process_driver implementation.
method drive_process.
data : registration_number
type registration_number_manager=>registration_number_type.
do iterations times.
call method registration_number_manager=>get_next_registration_number
importing
next_registration_number
= registration_number.
write: / 'Iteration :'
, sy-index
, / 'Registration number:'
, registration_number
.
enddo.
endmethod.
endclass.
parameters : iterate type process_driver=>iteration_type.
start-of-selection.
call method process_driver=>drive_process
exporting
iterations = iterate.
In this case all processing is contained in two static classes: class “registration_number_manager” is accessed by class “process_driver”, and class “process_driver” is accessed by the start-of-selection classic ABAP event block. Open your favorite editor, make a copy of the code above and execute it to confirm that it works properly and will produce a list of unique registration numbers based on a number you provide on the initial selection screen. Then follow along as we transform the static classes of this program to use different variations of the Singleton design pattern. Also, I recommend retaining each completed variation as its own separate program since the source code for some variations explored here will start by using the completed source code of a previous variation.
Singleton Variation 1a
Caller accesses sole instance directly through public static attribute of class.
This variation of singleton is the same model that had been used with the singletons created in Part 6 of the 6-part blog series Getting Comfortable using the Object-Oriented design model with ABAP.
Since there are two static classes in this program, we will transform each of them into corresponding singleton classes. We’ll start with static class “registration_number_manager”. Apply the following changes to transform it from a static class into a singleton class using variation 1a:
◈ Remove the qualifier “abstract” from its class definition.
◈ Include the qualifier “create private” in its class definition, following the qualifier “final”.
◈ Include the following two statements in the public section preceding the “class-methods” statement:
class-data : singleton type ref to registration_number_manager read-only.
class-methods: class_constructor.
◈ Change the definition of public method “get_next_registration_number” from static method to instance method by replacing “class-methods” with “methods”.
◈ Change the definition of private attribute “next_registration_number” from a static attribute to an instance attribute by replacing “class-data” with “data”.
◈ Include the following lines after the class implementation statement:
method class_constructor.
create object registration_number_manager=>singleton.
endmethod.
◈ In method “get_next_registration_number”, replace both occurrences of “registration_number_manager=>” with “me->”.
Afterward class “registration_number_manager” should look like this:
class registration_number_manager definition
final
create private.
public section.
types : registration_number_type
type n length 05.
class-data : singleton type ref to registration_number_manager read-only.
class-methods: class_constructor.
methods : get_next_registration_number
exporting
next_registration_number
type registration_number_manager=>registration_number_type.
private section.
data : next_registration_number
type registration_number_manager=>registration_number_type
value 1000.
endclass.
class registration_number_manager implementation.
method class_constructor.
create object registration_number_manager=>singleton.
endmethod.
method get_next_registration_number.
add 01 to me->next_registration_number.
next_registration_number = me->next_registration_number.
endmethod.
endclass.
A syntax check at this point should fail on the “call method” statement in method “drive_process” of class “process_driver”. Resolve this by changing the statement
call method registration_number_manager=>get_next_registration_number
to
call method registration_number_manager=>singleton->get_next_registration_number
Afterward a syntax check will pass. Executing the program at this point should prove that it works the same as it had prior to applying these changes.
Next, apply the following changes to transform “process_driver” from a static class into a singleton class using variation 1a:
◈ Remove the qualifier “abstract” from its class definition.
◈ Include the qualifier “create private” in its class definition, following the qualifier “final”.
◈ Include the following two statements in the public section preceding the “class-methods” statement:
class-data : singleton type ref to process_driver read-only.
class-methods: class_constructor.
◈ Change the definition of public method “drive_process” from static method to instance method by replacing “class-methods” with “methods”.
◈ Include the following lines after the class implementation statement:
method class_constructor.
create object process_driver=>singleton.
endmethod.
Afterward class “process_driver” should look like this:
class process_driver definition
final
create private.
public section.
types : iteration_type type n length 02.
class-data : singleton type ref to process_driver read-only.
class-methods: class_constructor.
methods : drive_process
importing
iterations
type process_driver=>iteration_type.
endclass.
class process_driver implementation.
method class_constructor.
create object process_driver=>singleton.
endmethod.
method drive_process.
data : registration_number
type registration_number_manager=>registration_number_type.
do iterations times.
call method registration_number_manager=>singleton->get_next_registration_number
importing
next_registration_number
= registration_number.
write: / 'Iteration :'
, sy-index
, / 'Registration number:'
, registration_number
.
enddo.
endmethod.
endclass.
A syntax check at this point should fail on the “call method” statement in the start-of-selection classic ABAP event block. Resolve this by changing the statement
call method process_driver=>drive_process
to
call method process_driver=>singleton->drive_process
Afterward a syntax check will pass. Executing the program at this point should prove that it works the same as it had prior to applying these changes.
Notice that method “drive_process” of class “process_driver” is invoked only from within the start-of-selection classic ABAP event block. Using this variation of the singleton enables the non-object-oriented statements of the program to remain devoid of defining a global variable to provide access to the singleton instance of class “process_driver”.
If you find yourself confused by how the statement
call method process_driver=>singleton->drive_process
could possibly work, refer to the explanation provided in this blog.
Here is how singleton variation 1a complies with the three criteria for defining a singleton:
◈ Class is defined with private instantiation.
Yes, via the “create private” defined on the class definition statement.
◈ Class is defined with static attribute defined as reference to an object of the very same class.
Yes, the static attribute is defined with public visibility and is assigned the “read-only” qualifier so that external entities can access it but cannot change its value.
◈ Class is defined with static method to provide access to the sole instance of the class.
Yes, the static constructor method creates the singleton instance, placing its reference into the public static attribute implicitly available to external entities.
Singleton Variation 2b
Caller must provide its own instance reference variable to accept reference to sole instance.
Here we are going to apply this singleton variation only to class “registration_number_manager”, but the same process can be applied to class “process_driver”. First, make a new copy using the sample program from above. Then apply the following changes to class “registration_number_manager” to transform it from a static class into a singleton class using variation 2b:
◈ Remove the qualifier “abstract” from its class definition.
◈ Include the qualifier “create private” in its class definition, following the qualifier “final”.
◈ Include the following statements in the pubic section preceding the “class-methods” statement:
class-methods: class_constructor.
class-methods: get_instance
exporting
instance
type ref to registration_number_manager.
◈ Change the definition of public method “get_next_registration_number” from static method to instance method by replacing “class-methods” with “methods”.
◈ Include the following statement in the private section preceding the “class-data” statement:
class-data : singleton type ref to registration_number_manager.
◈ Change the definition of private attribute “next_registration_number” from a static attribute to an instance attribute by replacing “class-data” with “data”.
◈ Include the following lines after the class implementation statement:
method class_constructor.
create object registration_number_manager=>singleton.
endmethod.
◈ In method “get_next_registration_number”, replace both occurrences of “registration_number_manager=>” with “me->”.
◈ Include the following lines after the last “endmethod” statement:
method get_instance.
instance = registration_number_manager=>singleton.
endmethod.
Afterward class “registration_number_manager” should look like this:
class registration_number_manager definition
final
create private.
public section.
types : registration_number_type
type n length 05.
class-methods: class_constructor.
class-methods: get_instance
exporting
instance
type ref to registration_number_manager.
methods : get_next_registration_number
exporting
next_registration_number
type registration_number_manager=>registration_number_type.
private section.
class-data : singleton type ref to registration_number_manager.
data : next_registration_number
type registration_number_manager=>registration_number_type
value 1000.
endclass.
class registration_number_manager implementation.
method class_constructor.
create object registration_number_manager=>singleton.
endmethod.
method get_next_registration_number.
add 01 to me->next_registration_number.
next_registration_number = me->next_registration_number.
endmethod.
method get_instance.
instance = registration_number_manager=>singleton.
endmethod.
endclass.
A syntax check at this point should fail on the “call method” statement in method “drive_process” of class “process_driver”. Resolve this by placing the following statement preceding the “do iterations times” statement:
data : registration_number_manager
type ref to registration_number_manager.
and by replacing the single statement:
call method registration_number_manager=>get_next_registration_number
importing
next_registration_number
= registration_number.
with the following two statements:
call method registration_number_manager=>get_instance
importing
instance = registration_number_manager.
call method registration_number_manager->get_next_registration_number
importing
next_registration_number
= registration_number.
Afterward a syntax check will pass. Executing the program at this point should prove that it works the same as it had prior to applying these changes.
Notice in this case that the user of the singleton class first needs to call a public static method of the singleton class to fetch a reference to the singleton object, providing its own reference variable for this activity, and then invoking the singleton object through that same reference variable.
Here is how singleton variation 2b complies with the three criteria for defining a singleton:
◈ Class is defined with private instantiation.
Yes, via the “create private” defined on the class definition statement.
◈ Class is defined with static attribute defined as reference to an object of the very same class.
Yes, the static attribute is defined with private visibility making it inaccessible to external entities.
◈ Class is defined with static method to provide access to the sole instance of the class.
Yes, the static constructor method creates the singleton instance, placing its reference into the private static attribute unavailable to external entities, and public static accessor method “get_instance” makes this reference available to external entities via exporting parameter.
Singleton Variation 2c
Caller may dispense with providing its own instance reference variable to accept reference to sole instance when using “chained method call” to access it.
Here again we are going to apply this singleton variation only to class “registration_number_manager”, but the same process can be applied to class “process_driver”. First, make a new copy using the variation 2b program just completed. Then apply the following changes to class “registration_number_manager” to transform it from a singleton class using variation 2b to a singleton class using variation 2c:
◈ Change the definition of public static method “get_instance” by replacing
exporting
instance
with
returning
value(instance)
Afterward class “registration_number_manager” should look like this:
class registration_number_manager definition
final
create private.
public section.
types : registration_number_type
type n length 05.
class-methods: class_constructor.
class-methods: get_instance
returning
value(instance)
type ref to registration_number_manager.
methods : get_next_registration_number
exporting
next_registration_number
type registration_number_manager=>registration_number_type.
private section.
class-data : singleton type ref to registration_number_manager.
data : next_registration_number
type registration_number_manager=>registration_number_type
value 1000.
endclass.
class registration_number_manager implementation.
method class_constructor.
create object registration_number_manager=>singleton.
endmethod.
method get_next_registration_number.
add 01 to me->next_registration_number.
next_registration_number = me->next_registration_number.
endmethod.
method get_instance.
instance = registration_number_manager=>singleton.
endmethod.
endclass.
A syntax check at this point should fail on the “call method get_instance” statement in method “drive_process” of class “process_driver”. Resolve this by applying the following change to method “drive_process”:
Replace the word “importing” in the statement
call method registration_number_manager=>get_instance
importing
instance = registration_number_manager.
with the word “receiving”, as in:
call method registration_number_manager=>get_instance
receiving
instance = registration_number_manager.
Afterward a syntax check will pass. Executing the program at this point should prove that it works the same as it had prior to applying these changes.
Whereas the change we applied works, it does not illustrate the most significant reason for having changed the signature of method get_instance of class registration_number_manager to return a value rather than export one. When the signature of get_instance is defined to return the instance, then we can use this method in a chained method call and eliminate the local variable defined to receive the reference to the instance.
To see how this works, do the following:
◈ In method “drive_process” of class “process_driver”, remove the definition for local variable “registration_number_manager”.
A syntax check at this point should fail on the “call method get_instance” statement in this method. Resolve this by applying the following changes to method “drive_process”:
◈ Replace the two statements
call method registration_number_manager=>get_instance
receiving
instance = registration_number_manager.
call method registration_number_manager->get_next_registration_number
importing
next_registration_number
= registration_number.
with the single statement
call method registration_number_manager=>get_instance( )->get_next_registration_number(
importing
next_registration_number
= registration_number
).
Afterward a syntax check will pass. Executing the program at this point should prove that it works the same as it had prior to applying these changes.
Notice that with this variation of singleton the caller no longer is required to provide the local variable previously used to retrieve a reference to the singleton object.
Here is how singleton variation 2c complies with the three criteria for defining a singleton:
◈ Class is defined with private instantiation.
Yes, via the “create private” defined on the class definition statement.
◈ Class is defined with static attribute defined as reference to an object of the very same class.
Yes, the static attribute is defined with private visibility making it inaccessible to external entities.
◈ Class is defined with static method to provide access to the sole instance of the class.
Yes, the static constructor method creates the singleton instance, placing its reference into the private static attribute unavailable to external entities, and public static accessor method “get_instance” makes this reference available to external entities via returning parameter.
Singleton Variation 3b
Caller must provide its own instance reference variable to accept reference to sole instance.
Again we are going to apply this singleton variation only to class “registration_number_manager”, but the same process can be applied to class “process_driver”. First, make a new copy using the variation 2b program just completed. Then apply the following changes to class “registration_number_manager” to transform it from a singleton class using variation 2b to a singleton class using variation 3b:
◈ Remove the “class_constructor” definition and implementation.
◈ Include the following three statements as the first statements of method “get_instance”:
if registration_number_manager=>singleton is not bound.
create object registration_number_manager=>singleton.
endif.
Afterward class “registration_number_manager” should look like this:
class registration_number_manager definition
final
create private.
public section.
types : registration_number_type
type n length 05.
class-methods: get_instance
exporting
instance
type ref to registration_number_manager.
methods : get_next_registration_number
exporting
next_registration_number
type registration_number_manager=>registration_number_type.
private section.
class-data : singleton type ref to registration_number_manager.
data : next_registration_number
type registration_number_manager=>registration_number_type
value 1000.
endclass.
class registration_number_manager implementation.
method get_next_registration_number.
add 01 to me->next_registration_number.
next_registration_number = me->next_registration_number.
endmethod.
method get_instance.
if registration_number_manager=>singleton is not bound.
create object registration_number_manager=>singleton.
endif.
instance = registration_number_manager=>singleton.
endmethod.
endclass.
A syntax check at this point should pass. Executing the program at this point should prove that it works the same as it had prior to applying these changes.
Here we are using what is known as lazy initialization. A singleton instance is not created until a caller explicitly invokes the static accessor method “get_instance”.
Here is how singleton variation 3b complies with the three criteria for defining a singleton:
◈ Class is defined with private instantiation.
Yes, via the “create private” defined on the class definition statement.
◈ Class is defined with static attribute defined as reference to an object of the very same class.
Yes, the static attribute is defined with private visibility making it inaccessible to external entities.
◈ Class is defined with static method to provide access to the sole instance of the class.
Yes, the public static accessor method “get_instance” makes this reference available to external entities via exporting parameter, using lazy initialization to create an instance if the reference is not yet bound.
Singleton Variation 3c
Caller may dispense with providing its own instance reference variable to accept reference to sole instance when using “chained method call” to access it.
Again we are going to apply this singleton variation only to class “registration_number_manager”, but the same process can be applied to class “process_driver”. First, make a new copy using the variation 2c program just completed. Then apply the following changes to class “registration_number_manager” to transform it from a singleton class using variation 2c to a singleton class using variation 3c (indeed, these changes are identical the changes applied between variations 2b and 3b):
◈ Remove the “class_constructor” definition and implementation.
◈ Include the following three statements as the first statements of method “get_instance”:
if registration_number_manager=>singleton is not bound.
create object registration_number_manager=>singleton.
endif.
Afterward class “registration_number_manager” should look like this:
class registration_number_manager definition
final
create private.
public section.
types : registration_number_type
type n length 05.
class-methods: get_instance
returning
value(instance)
type ref to registration_number_manager.
methods : get_next_registration_number
exporting
next_registration_number
type registration_number_manager=>registration_number_type.
private section.
class-data : singleton type ref to registration_number_manager.
data : next_registration_number
type registration_number_manager=>registration_number_type
value 1000.
endclass.
class registration_number_manager implementation.
method get_next_registration_number.
add 01 to me->next_registration_number.
next_registration_number = me->next_registration_number.
endmethod.
method get_instance.
if registration_number_manager=>singleton is not bound.
create object registration_number_manager=>singleton.
endif.
instance = registration_number_manager=>singleton.
endmethod.
endclass.
A syntax check at this point should pass. Executing the program at this point should prove that it works the same as it had prior to applying these changes.
Here again we are using lazy initialization. A singleton instance is not created until a caller explicitly invokes the static accessor method get_instance.
Here is how singleton variation 3c complies with the three criteria for defining a singleton:
◈ Class is defined with private instantiation.
Yes, via the “create private” defined on the class definition statement.
◈ Class is defined with static attribute defined as reference to an object of the very same class.
Yes, the static attribute is defined with private visibility making it inaccessible to external entities.
◈ Class is defined with static method to provide access to the sole instance of the class.
Yes, the public static accessor method “get_instance” makes this reference available to external entities via returning parameter, using lazy initialization to create an instance if the reference is not yet bound.
Analysis of the singleton variations
To recap, the singleton variations presented here are these:
1a – Caller accesses sole instance directly through public static attribute of class.
2b – Caller must provide its own instance reference variable to accept reference to sole instance.
2c – Caller may dispense with providing its own instance reference variable to accept reference to sole instance when using “chained method call” to access it.
3b – Same as 2b with lazy initialization.
3c – Same as 2c with lazy initialization.
Variations 2b and 3b seem to be the least useful since the user of a singleton defined this way must provide its own receiver for the singleton instance when calling the public static method providing the instance (method “get_instance” in this case). This would be particularly egregious when invoking such a singleton from a classic ABAP event block since it would require the use of a global variable to receive the reference. Variations 2c and 3c also would accommodate this capability if the user chose to provide its own receiver, but these variations also provide the ability to use a chained method call to invoke the instance methods of the singleton, bypassing the necessity for the caller to provide its own receiver field to accept a reference to the singleton.
Variations 3b and 3c describe singletons using the lazy initialization technique to instantiate the sole object with the first call to the “get_instance” method. These variations have no static constructor creating the sole instance. By contrast, variations 2b and 2c do have static constructors creating the sole instance, static constructors that would be triggered upon entering a block of ABAP code containing an ABAP statement with a reference to a static member of the class, regardless whether that statement is actually executed. Accordingly, the lazy initialization used with singleton variations 3b and 3c would prevent a sole instance from being created unnecessarily if the execution path through the block of code results in the statement referencing the singleton being bypassed.
Variations 2c and 3c require two method invocations to reach an instance method of the sole object when using the chained method call technique as shown in the format below:
call method some_class=>get_instance( )->some_instance_method( ).
The first method call is the one to the static method “some_class=>get_instance”, which returns a reference to the sole instance, which is then used as the object on which the second, instance method call to “some_instance_method” is invoked. If there were to be repeated calls to the instance methods of the sole instance of class “some_class” defined as a variation 2c or 3c singleton, then the chained method call could be replaced with a single initial call to static method “some_class=>get_instance”, saving the instance reference it receives in a local variable and then using this local variable to invoke the repeated calls to the singleton’s instance methods, thus reducing the number of method calls necessary to access the instance methods of the sole instance.
No comments:
Post a Comment