Application Development Blog Posts
Learn and share on deeper, cross technology development topics such as integration and connectivity, automation, cloud extensibility, developing at scale, and security.
cancel
Showing results for 
Search instead for 
Did you mean: 
This blog represents part 6 of the 6-part series Getting comfortable using the Object-Oriented design model with ABAP.

Part 1 – Transforming a simple procedural model into its equivalent OO design, focusing on the OO principles of Encapsulation and Abstraction.


Part 2 – Exploring Abstraction further by refactoring the program to transform selected static classes into instantiable classes.


Part 3 – Refactoring the program further to take advantage of the OO principle of Inheritance.


Part 4 – Adhering to the Single Responsibility Principle by refining the program to restrict each class to do only what it is intended to do.


Part 5 – Removing classes that no longer serve a purpose.


Part 6 – Introducing the Singleton OO design pattern, resulting in elimination of all static classes.


To recap from the preceding blog, we took a program having virtually identical classes and removed the unnecessary duplication. Here is the source code as we left it in the previous blog:
report.
interface data_exchangeable.
types : row_counter type n length 02.
types : email_recipient
type adr6-smtp_addr.
endinterface.
class flight_records_retriever definition
abstract
final.
public section.
types : record_list type standard table of sflight.
class-methods: retrieve_records
importing
row_count
type data_exchangeable=>row_counter
exporting
record_stack
type flight_records_retriever=>record_list
.
endclass.
class flight_records_retriever implementation.
method retrieve_records.
select *
into table record_stack
from sflight
up to row_count rows.
endmethod.
endclass.
class carrier_records_retriever definition
abstract
final.
public section.
types : record_list type standard table of scarr.
class-methods: retrieve_records
importing
row_count
type data_exchangeable=>row_counter
exporting
record_stack
type carrier_records_retriever=>record_list
.
endclass.
class carrier_records_retriever implementation.
method retrieve_records.
select *
into table record_stack
from scarr
up to row_count rows.
endmethod.
endclass.
class booking_records_retriever definition
abstract
final.
public section.
types : record_list type standard table of sbook.
class-methods: retrieve_records
importing
row_count
type data_exchangeable=>row_counter
exporting
record_stack
type booking_records_retriever=>record_list
.
endclass.
class booking_records_retriever implementation.
method retrieve_records.
select *
into table record_stack
from sbook
up to row_count rows.
endmethod.
endclass.
class excel_spreadsheet_manager definition
final.
public section.
methods : copy_table_to_excel_worksheet
importing
source_stack
type standard table
source_description
type string
raising
zcx_excel
, send_excel_via_email
importing
recipient
type data_exchangeable=>email_recipient
.
private section.
data : excel type ref to zcl_excel.
endclass.
class excel_spreadsheet_manager implementation.
method copy_table_to_excel_worksheet.
constants : first_column type char1 value 'A'
.
data : worksheet type ref to zcl_excel_worksheet
, worksheet_title
type zexcel_sheet_title
, table_settings type zexcel_s_table_settings
.
table_settings-table_style = zcl_excel_table=>builtinstyle_medium2.
table_settings-show_row_stripes
= abap_true.
table_settings-nofilters = abap_true.
table_settings-top_left_column
= first_column.
table_settings-top_left_row = 01.
if excel is not bound.
create object excel.
worksheet = excel->get_active_worksheet( ).
else.
worksheet = excel->add_new_worksheet( ).
endif.
worksheet_title = source_description.
worksheet->set_title( worksheet_title ).
worksheet->bind_table(
ip_table = source_stack
is_table_settings = table_settings
).
endmethod.
method send_excel_via_email.
constants : excel_file_type
type string value '.xlsx'
, file_name_parameter
type string value '&SO_FILENAME='
.
data : excel_writer type ref to zif_excel_writer
, excel_as_xstring
type xstring
, excel_as_xstring_bytecount
type i
, excel_as_solix_stack
type solix_tab
, mail_send_request
type ref to cl_bcs
, mail_message type ref to cl_document_bcs
, any_bcs_exception
type ref to cx_bcs
, diagnostic type string
, mail_title type so_obj_des
, mail_text_stack
type soli_tab
, mail_text_entry
like line
of mail_text_stack
, mail_attachment_subject
type sood-objdes
, mail_attachment_bytecount
type sood-objlen
, mail_attachment_header_stack
type soli_tab
, mail_attachment_header_entry
like line of mail_attachment_header_stack
, internet_email_recipient
type ref to if_recipient_bcs
, successful_send
type abap_bool
, file_name type string
.
" Much of the code here was lifted from method send_mail of
" class lcl_ouput, defined in object ZDEMO_EXCEL_OUTPUTOPT_INCL:
concatenate sy-repid " this report name
sy-datum " current date
sy-uzeit " current time
excel_file_type " excel file extension
into file_name.
mail_title = file_name.
mail_attachment_subject = file_name.
mail_text_entry = 'See attachment'.
append mail_text_entry
to mail_text_stack.
concatenate file_name_parameter
file_name
into mail_attachment_header_entry.
append mail_attachment_header_entry
to mail_attachment_header_stack.
create object excel_writer type zcl_excel_writer_2007.
excel_as_xstring = excel_writer->write_file( excel ).
excel_as_solix_stack = cl_bcs_convert=>xstring_to_solix( iv_xstring = excel_as_xstring ).
excel_as_xstring_bytecount = xstrlen( excel_as_xstring ).
mail_attachment_bytecount = excel_as_xstring_bytecount.
try.
mail_message = cl_document_bcs=>create_document(
i_type = 'RAW' "#EC NOTEXT
i_text = mail_text_stack
i_subject = mail_title
).
mail_message->add_attachment(
i_attachment_type = 'XLS' "#EC NOTEXT
i_attachment_subject = mail_attachment_subject
i_attachment_size = mail_attachment_bytecount
i_att_content_hex = excel_as_solix_stack
i_attachment_header = mail_attachment_header_stack
).
mail_send_request = cl_bcs=>create_persistent( ).
mail_send_request->set_document( mail_message ).
internet_email_recipient = cl_cam_address_bcs=>create_internet_address( recipient ).
mail_send_request->add_recipient( internet_email_recipient ).
successful_send = mail_send_request->send( ).
commit work.
if successful_send eq abap_false.
message i500(sbcoms) with recipient.
else.
message s022(so).
message 'Document ready to be sent - Check SOST' type 'I'.
endif.
catch cx_bcs into any_bcs_exception.
diagnostic = any_bcs_exception->if_message~get_text( ).
message diagnostic type 'I'.
endtry.
endmethod.
endclass.
class report definition
final.
public section.
methods : present_report
changing
record_stack
type standard table
.
endclass.
class report implementation.
method present_report.
data : alv_report type ref to cl_salv_table
.
try.
call method cl_salv_table=>factory
importing
r_salv_table = alv_report
changing
t_table = record_stack.
catch cx_salv_msg.
return.
endtry.
alv_report->display( ).
endmethod.
endclass.
class process_driver definition
abstract
final.
public section.
class-methods: drive_process
importing
row_count
type data_exchangeable=>row_counter
recipient
type data_exchangeable=>email_recipient
.
endclass.
class process_driver implementation.
method drive_process.
data : report type ref to report
, excel_spreadsheet_manager
type ref to excel_spreadsheet_manager
, flight_stack type flight_records_retriever=>record_list
, carrier_stack type carrier_records_retriever=>record_list
, booking_stack type booking_records_retriever=>record_list
.
create object: report
, excel_spreadsheet_manager
.
call method flight_records_retriever=>retrieve_records
exporting
row_count = row_count
importing
record_stack = flight_stack.
call method carrier_records_retriever=>retrieve_records
exporting
row_count = row_count
importing
record_stack = carrier_stack.
call method booking_records_retriever=>retrieve_records
exporting
row_count = row_count
importing
record_stack = booking_stack.
call method report->present_report changing record_stack = flight_stack.
call method report->present_report changing record_stack = carrier_stack.
call method report->present_report changing record_stack = booking_stack.
try.
call method excel_spreadsheet_manager->copy_table_to_excel_worksheet
exporting
source_stack = flight_stack
source_description = 'Flights'.
call method excel_spreadsheet_manager->copy_table_to_excel_worksheet
exporting
source_stack = carrier_stack
source_description = 'Carriers'.
call method excel_spreadsheet_manager->copy_table_to_excel_worksheet
exporting
source_stack = booking_stack
source_description = 'Bookings'.
catch zcx_excel ##NO_HANDLER.
endtry.
call method excel_spreadsheet_manager->send_excel_via_email exporting recipient = recipient.
endmethod.
endclass.
class email_address_resolver definition
abstract
final.
public section.
class-methods: resolve_email_address
importing
userid
type syuname
exporting
email_address
type data_exchangeable=>email_recipient
.
endclass.
class email_address_resolver implementation.
method resolve_email_address.
select single smtp_addr
into email_address
from adr6 ##WARN_OK
inner join
usr21 on usr21~persnumber eq adr6~persnumber
where usr21~bname eq userid.
endmethod.
endclass.
parameters : rowcount type data_exchangeable=>row_counter.
parameters : recipien type data_exchangeable=>email_recipient.
initialization.
call method email_address_resolver=>resolve_email_address
exporting
userid = sy-uname
importing
email_address = recipien.
start-of-selection.
call method process_driver=>drive_process
exporting
row_count = rowcount
recipient = recipien.

Open your favorite ABAP editor, make a copy of this ABAP program and follow along as we apply changes to eliminate all static classes. For those who do not have ABAP2XLSX available at their site, replace class excel_spreadsheet_manager with the following source code:
class excel_spreadsheet_manager        definition
final.
public section.
methods : copy_table_to_excel_worksheet
importing
source_stack
type standard table
source_description
type string
raising
zcx_excel
, send_excel_via_email
importing
recipient
type data_exchangeable=>email_recipient
.
private section.
data : excel type string.
endclass.
class excel_spreadsheet_manager implementation.
method copy_table_to_excel_worksheet.
data : source_stack_lines type string.
describe table source_stack lines source_stack_lines.
concatenate excel
source_stack_lines
source_description
into excel separated by space.
endmethod.
method send_excel_via_email.
data : message type string.
concatenate excel
'would be sent to'
recipient
into message separated by space.
message message type 'I'.
endmethod.
endclass.

and include the following local exception class definition after the report statement:
class zcx_excel                        definition
inheriting from cx_static_check.
endclass.

Motivation

As noted in one of the previous blogs in this series, static classes offer ABAP programmers a stepping stone toward becoming more familiar with object-oriented principles due to their similarity with ABAP function groups, specifically:

  • Function groups and static classes both offer only a single copy of the attributes and data fields they define.

  • Function groups and static classes both are loaded into storage with the first reference to one of its members.

  • Methods of static classes can be invoked via the class name, similar to the way function modules can be invoked simply via the function module name.


Though static classes are easier to grasp for those new to OO concepts, they have limitations to what they can offer with a truly object-oriented design. Indeed, Rule 5.3 of the book Official ABAP Programming Guidelines (https://www.sap-press.com/official-abap-programming-guidelines_2093/), states (page 162):

Rule 5.3: Do Not Use Static Classes


Preferably use objects instead of static classes. If you don’t want to have a multiple instantiation, you can use singletons.


The book goes on to provide further details about why static classes should be avoided, amongst them:

  • Explicit object creation is essential for object-oriented programming.

  • The static constructor offers only limited functionality.

  • Static classes lack support for polymorphism.


Understanding multiple instantiations

Let’s take a moment to understand the phrase “don’t want to have a multiple instantiation” appearing in the paragraph accompanying Rule 5.3. Static classes have no concept of instantiation. Any attributes defined for a static class will exist only once in storage while the program is executing. In contrast, for a class offering instantiation it is possible to have multiple instances of the class existing simultaneously while the program is executing, in each case the class instance having been created by a create object statement (or some variation of this statement, such as the operator new).

Let’s see this with an example. Suppose we have a static class car as defined below:
class car                              definition
abstract
final.
public section.
class-methods: set_color
importing
color
type string
, set_year
importing
year
type string
.
private section.
class-data : color type string
, year type string
.
endclass.
class car implementation.
method set_color.
car=>color = color.
endmethod.
method set_year.
car=>year = year.
endmethod.
endclass.

Notice that the two methods, set_color and set_year, are static methods since they are defined with a class-methods statement. Notice also that the two attributes, color and year, are static attributes since they are defined with a class-data statement. In fact, all attributes and methods of this class are statically defined, rendering this class a static class. It is not necessary – indeed, it is incorrect – to instantiate such a class; we simply can begin to use the class in our code, as in:
call method car=>set_color exporting color = ‘silver’.
call method car=>set_year exporting year = ‘2010’.

The attributes color and year of class car exist only once in storage for the duration of the program in which these statements appear.

The problem with class car being defined as a static class is that we can use it to track the color and year of only one car. If we need to track color and year for a multitude of car objects, then we would need to change the class such that a) it offers instantiation, and b) we create multiple instances of the class to represent the multiple instances of cars we want to track. Here is how the car class would need to be changed to accommodate multiple instantiations:
class car                              definition
final.
public section.
methods : set_color
importing
color
type string
, set_year
importing
year
type string
.
private section.
data : color type string
, year type string
.
endclass.
class car implementation.
method set_color.
me->color = color.
endmethod.
method set_year.
me->year = year.
endmethod.
endclass.

The differences between the static version and the version above are:

  • The qualifier abstract was removed from the class definition statement.

  • The class-methods statement was changed to a methods statement.

  • The class-data statement was changed to a data statement.

  • The two methods refer to their respective attributes using the instance self-reference (me) instead of the class name and using the instance selector (->) instead of the class selector (=>).


Now we are able to create multiple instances of cars and keep track their respective colors and years, as in:
  data           : car_01         type ref to car
, car_02 type ref to car
, car_03 type ref to car
.
create object: car_01
, car_02
, car_03
.
call method car_01->set_color exporting color = 'silver'.
call method car_01->set_year exporting year = '2010'.
call method car_02->set_color exporting color = 'maroon'.
call method car_02->set_year exporting year = '2014'.
call method car_03->set_color exporting color = 'green'.
call method car_03->set_year exporting year = '2015'.

Because the car class is no longer static, its attributes color and year exist once per instance of class car instead of only once per program execution.

Singletons

Let’s also take a moment to understand the phrase “you can use singletons” appearing in the paragraph accompanying Rule 5.3. A 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).

An instance class does not require multiple instances to exist during program execution, it merely enables this capability. As per one of the previous blogs in this series, when we converted class excel_spreadsheet_manager from a static class to an instance class, we gained the capability to instantiate this class more than once during program execution. However, you’ll notice that our example program instantiates this class only once. Indeed, for all classes in our example program facilitating multiple instantiation, we have never taken advantage to instantiate any one of them more than once. This is because we need only a single instance of these classes to handle our processing requirements. Since for these classes we “don’t want to have a multiple instantiation”, as stated in the paragraph accompanying Rule 5.3, then we “can use singletons” to insure there is one and only one instance of these classes during execution.

Converting our first class from static to Singleton

To get started, let’s take static class flight_records_retriever and convert it into a singleton class by making the following changes:

  • Change its class definition statement, removing the qualifier abstract and adding the qualifier create private following the qualifier final.

  • Include in its public section the following statements preceding the definition of method retrieve_records:


    class-data   : singleton      type ref to flight_records_retriever read-only.
class-methods: class_constructor.


  • Change method retrieve_records from a static method to an instance method by replacing “class-methods” with “methods”.

  • Include after the class implementation statement the following static constructor method:


  method class_constructor.
create object flight_records_retriever=>singleton.
endmethod.

Upon completing these changes, class flight_records_retriever should look like this:
class flight_records_retriever         definition
final
create private.
public section.
types : record_list type standard table of sflight.
class-data : singleton type ref to flight_records_retriever read-only.
class-methods: class_constructor.
methods : retrieve_records
importing
row_count
type data_exchangeable=>row_counter
exporting
record_stack
type flight_records_retriever=>record_list
.
endclass.
class flight_records_retriever implementation.
method class_constructor.
create object flight_records_retriever=>singleton.
endmethod.
method retrieve_records.
select *
into table record_stack
from sflight
up to row_count rows.
endmethod.
endclass.

At this point a syntax check will fail on the call to method retrieve_records of class flight_records_retriever. Correct this by changing the statement:
    call method flight_records_retriever=>retrieve_records

to
    call method flight_records_retriever=>singleton->retrieve_records

Afterward a syntax check will pass.

Let’s review what we’ve done. Due to including the qualifier create private on the class definition, we now have a class that cannot be instantiated other than by this class itself, and indeed the instantiation of this class occurs via the static constructor (class_constructor), which is invoked immediately upon the first reference to this class in the code, placing a reference to the instance of class flight_records_retriever it creates into the new static attribute called singleton. Notice that this class now has both static and instance members. There will be only one instance of this class ever created because the instantiation occurs during the static constructor, which will be executed exactly once per program execution. To access this single instance, we can refer to the public static attribute flight_records_retriever=>singleton, an attribute we can access but cannot change due to the use of the qualifier read-only on its definition.

Presto. A former static class now exists as a singleton, conforming with Rule 5.3.

Further explanation of Singleton

There are other ways to define a class as a singleton than how it is shown in this example but most ways are simply variations on a theme. With the variation we are using here, we changed the only static member the class formerly had – the static method retrieve_records – into an instance method. Having done that, we effectively removed all static members from the class, but then we went ahead and added two new static members to it: a public static attribute called singleton and a static constructor. ABAP provides the reserved method name “class_constructor” to designate a static constructor and it always must be defined with public visibility. The static attribute we added – singleton – is a reference variable to an instance of the very same class in which it is defined. Notice that the implementation of the static constructor consists of only one statement creating an instance of this class and placing the reference to it into the new static attribute we added.

While the method name class_constructor is required when defining a static constructor, the name of the static attribute into which the instance reference is placed can be any name conforming to rules of variable naming. I prefer to call this field “singleton” to reinforce the role the class plays since any programmer subsequently maintaining this program can immediately recognize the design pattern this class employs. Notice also that this new static attribute is accompanied by the “read-only” qualifier. This means that any external entity has read access to this static attribute, but only the class itself is able to change its value, and the only time this value is changed by this class is during the execution of its static constructor.

Some may find it difficult to understand how the statement
call method flight_records_retriever=>singleton->retrieve_records ...

is capable of invoking an instance of a class that we did not explicitly create prior to reaching this statement. Indeed, it may be difficult even to grasp the concept of how an instance of a class can be created at all when the class definition indicates, via the qualifier create private, that only the class itself is capable of creating instances of the class. Let's further examine both of these concepts.

Understanding the concept of instantiating a Singleton

Until we have a better understanding of how this works, we might find ourselves wrestling with the counterintuitive quandary expressed by this question: How can instances of a class be created when only the class itself is capable of creating such instances? The solution to this enigma is that the class needs to be composed of both static and instance members. When the definition of a class indicates create private, it is the static members of a class that facilitate creating instances of the very same class since the static members of a class are available during program execution even when no instances of the class exist.  Indeed, a class defined as a singleton typically contains only as many static members as necessary to manage access to its singleton instance.

Understanding the mechanics of invoking a Singleton

Now let's examine how the statement
call method flight_records_retriever=>singleton->retrieve_records ...

enables us to access the singleton instance of the flight_records_retriever class before we even caused an instance to be explicitly created prior to reaching this statement. Indeed, here we are accessing an instance method of a class for which our program does not even provide a reference to a corresponding instance. How is this even possible?

Let's first notice that the string following “call method” is composed of the name of the class (flight_records_retriever), followed by a class component selector (=>), followed by the name of a public static attribute of the class (singleton), followed by an instance component selector (->), followed by the name of a public instance method (retrieve_records), all with no intervening spaces between them, effectively representing a compound reference due to the use of more than one component selector in the name.

Although the runtime environment does not behave exactly this way, it is helpful to conceive the execution of this statement in the following way:

  1. After moving past the “call method” words starting this statement, the statement parser facilitating program execution reaches the portion of the statement operand containing “flight_records_retriever=>”. Recognizing that the class selector => denotes a reference to a class name, the statement parser at that point requests the runtime environment to load into storage, if not already loaded, the class name that precedes the class selector, in this case flight_records_retriever. Indeed, since this is the first reference to this class, this will be the point at which the runtime environment loads class flight_records_retriever. At that moment, its static constructor will be invoked, which, according to the implementation for this method, will initialize static attribute singleton with a reference to an object of type flight_records_retriever.

  2. Next, the statement parser will reach the portion of the statement operand containing “singleton->”. Recognizing that the instance selector -> denotes an instance reference, the reference field preceding the instance selector, in this case singleton, defined as a static attribute of the flight_records_retriever class, is inspected to determine the type of the instance it holds, in this case a reference to a flight_records_retriever instance. By this time, a corresponding object of type flight_records_retriever already will have been instantiated into this static attribute through the completion of the static constructor method, invoked during the parsing of the previous portion of this statement operand.

  3. Next, the statement parser will reach the portion of the statement operand containing “retrieve_records”, which it will recognize as the instance method of class flight_records_retriever to be invoked through the instance reference held in publicly available static reference field singleton and will parse the remainder of the statement to resolve the parameters to be exchanged with this instance method.


Accordingly, using this variation of a Singleton, we are able to invoke the instance methods of the singleton object directly through the public static attribute provided by the class itself.  If this very same statement were to be encountered later during the execution of this program, it would go through the same 3 steps noted above, but this time the runtime environment would not need
to load the class into storage since that already had been done, and as a consequence the static constructor of the class would not be executed again.

Converting other classes from static to Singleton

Next, make the same relative changes to classes carrier_records_retriever and booking_records_retriever as were made for class flight_records_retriever. Afterward a syntax check still will pass.

Then make the same relative changes to classes process_driver and email_address_resolver. Afterward a syntax check still will pass.

Executing the program at this point should prove that it still works as before.

Summary

We’ve made enough changes for now and the final image of the code looks like this:
report.
interface data_exchangeable.
types : row_counter type n length 02.
types : email_recipient
type adr6-smtp_addr.
endinterface.
class flight_records_retriever definition
final
create private.
public section.
types : record_list type standard table of sflight.
class-data : singleton type ref to flight_records_retriever read-only.
class-methods: class_constructor.
methods : retrieve_records
importing
row_count
type data_exchangeable=>row_counter
exporting
record_stack
type flight_records_retriever=>record_list
.
endclass.
class flight_records_retriever implementation.
method class_constructor.
create object flight_records_retriever=>singleton.
endmethod.
method retrieve_records.
select *
into table record_stack
from sflight
up to row_count rows.
endmethod.
endclass.
class carrier_records_retriever definition
final
create private.
public section.
types : record_list type standard table of scarr.
class-data : singleton type ref to carrier_records_retriever read-only.
class-methods: class_constructor.
methods : retrieve_records
importing
row_count
type data_exchangeable=>row_counter
exporting
record_stack
type carrier_records_retriever=>record_list
.
endclass.
class carrier_records_retriever implementation.
method class_constructor.
create object carrier_records_retriever=>singleton.
endmethod.
method retrieve_records.
select *
into table record_stack
from scarr
up to row_count rows.
endmethod.
endclass.
class booking_records_retriever definition
final
create private.
public section.
types : record_list type standard table of sbook.
class-data : singleton type ref to booking_records_retriever read-only.
class-methods: class_constructor.
methods : retrieve_records
importing
row_count
type data_exchangeable=>row_counter
exporting
record_stack
type booking_records_retriever=>record_list
.
endclass.
class booking_records_retriever implementation.
method class_constructor.
create object booking_records_retriever=>singleton.
endmethod.
method retrieve_records.
select *
into table record_stack
from sbook
up to row_count rows.
endmethod.
endclass.
class excel_spreadsheet_manager definition
final.
public section.
methods : copy_table_to_excel_worksheet
importing
source_stack
type standard table
source_description
type string
raising
zcx_excel
, send_excel_via_email
importing
recipient
type data_exchangeable=>email_recipient
.
private section.
data : excel type ref to zcl_excel.
endclass.
class excel_spreadsheet_manager implementation.
method copy_table_to_excel_worksheet.
constants : first_column type char1 value 'A'
.
data : worksheet type ref to zcl_excel_worksheet
, worksheet_title
type zexcel_sheet_title
, table_settings type zexcel_s_table_settings
.
table_settings-table_style = zcl_excel_table=>builtinstyle_medium2.
table_settings-show_row_stripes
= abap_true.
table_settings-nofilters = abap_true.
table_settings-top_left_column
= first_column.
table_settings-top_left_row = 01.
if excel is not bound.
create object excel.
worksheet = excel->get_active_worksheet( ).
else.
worksheet = excel->add_new_worksheet( ).
endif.
worksheet_title = source_description.
worksheet->set_title( worksheet_title ).
worksheet->bind_table(
ip_table = source_stack
is_table_settings = table_settings
).
endmethod.
method send_excel_via_email.
constants : excel_file_type
type string value '.xlsx'
, file_name_parameter
type string value '&SO_FILENAME='
.
data : excel_writer type ref to zif_excel_writer
, excel_as_xstring
type xstring
, excel_as_xstring_bytecount
type i
, excel_as_solix_stack
type solix_tab
, mail_send_request
type ref to cl_bcs
, mail_message type ref to cl_document_bcs
, any_bcs_exception
type ref to cx_bcs
, diagnostic type string
, mail_title type so_obj_des
, mail_text_stack
type soli_tab
, mail_text_entry
like line
of mail_text_stack
, mail_attachment_subject
type sood-objdes
, mail_attachment_bytecount
type sood-objlen
, mail_attachment_header_stack
type soli_tab
, mail_attachment_header_entry
like line of mail_attachment_header_stack
, internet_email_recipient
type ref to if_recipient_bcs
, successful_send
type abap_bool
, file_name type string
.
" Much of the code here was lifted from method send_mail of
" class lcl_ouput, defined in object ZDEMO_EXCEL_OUTPUTOPT_INCL:
concatenate sy-repid " this report name
sy-datum " current date
sy-uzeit " current time
excel_file_type " excel file extension
into file_name.
mail_title = file_name.
mail_attachment_subject = file_name.
mail_text_entry = 'See attachment'.
append mail_text_entry
to mail_text_stack.
concatenate file_name_parameter
file_name
into mail_attachment_header_entry.
append mail_attachment_header_entry
to mail_attachment_header_stack.
create object excel_writer type zcl_excel_writer_2007.
excel_as_xstring = excel_writer->write_file( excel ).
excel_as_solix_stack = cl_bcs_convert=>xstring_to_solix( iv_xstring = excel_as_xstring ).
excel_as_xstring_bytecount = xstrlen( excel_as_xstring ).
mail_attachment_bytecount = excel_as_xstring_bytecount.
try.
mail_message = cl_document_bcs=>create_document(
i_type = 'RAW' "#EC NOTEXT
i_text = mail_text_stack
i_subject = mail_title
).
mail_message->add_attachment(
i_attachment_type = 'XLS' "#EC NOTEXT
i_attachment_subject = mail_attachment_subject
i_attachment_size = mail_attachment_bytecount
i_att_content_hex = excel_as_solix_stack
i_attachment_header = mail_attachment_header_stack
).
mail_send_request = cl_bcs=>create_persistent( ).
mail_send_request->set_document( mail_message ).
internet_email_recipient = cl_cam_address_bcs=>create_internet_address( recipient ).
mail_send_request->add_recipient( internet_email_recipient ).
successful_send = mail_send_request->send( ).
commit work.
if successful_send eq abap_false.
message i500(sbcoms) with recipient.
else.
message s022(so).
message 'Document ready to be sent - Check SOST' type 'I'.
endif.
catch cx_bcs into any_bcs_exception.
diagnostic = any_bcs_exception->if_message~get_text( ).
message diagnostic type 'I'.
endtry.
endmethod.
endclass.
class report definition
final.
public section.
methods : present_report
changing
record_stack
type standard table
.
endclass.
class report implementation.
method present_report.
data : alv_report type ref to cl_salv_table
.
try.
call method cl_salv_table=>factory
importing
r_salv_table = alv_report
changing
t_table = record_stack.
catch cx_salv_msg.
return.
endtry.
alv_report->display( ).
endmethod.
endclass.
class process_driver definition
final
create private.
public section.
class-data : singleton type ref to process_driver read-only.
class-methods: class_constructor.
methods : drive_process
importing
row_count
type data_exchangeable=>row_counter
recipient
type data_exchangeable=>email_recipient
.
endclass.
class process_driver implementation.
method class_constructor.
create object process_driver=>singleton.
endmethod.
method drive_process.
data : report type ref to report
, excel_spreadsheet_manager
type ref to excel_spreadsheet_manager
, flight_stack type flight_records_retriever=>record_list
, carrier_stack type carrier_records_retriever=>record_list
, booking_stack type booking_records_retriever=>record_list
.
create object: report
, excel_spreadsheet_manager
.
call method flight_records_retriever=>singleton->retrieve_records
exporting
row_count = row_count
importing
record_stack = flight_stack.
call method carrier_records_retriever=>singleton->retrieve_records
exporting
row_count = row_count
importing
record_stack = carrier_stack.
call method booking_records_retriever=>singleton->retrieve_records
exporting
row_count = row_count
importing
record_stack = booking_stack.
call method report->present_report changing record_stack = flight_stack.
call method report->present_report changing record_stack = carrier_stack.
call method report->present_report changing record_stack = booking_stack.
try.
call method excel_spreadsheet_manager->copy_table_to_excel_worksheet
exporting
source_stack = flight_stack
source_description = 'Flights'.
call method excel_spreadsheet_manager->copy_table_to_excel_worksheet
exporting
source_stack = carrier_stack
source_description = 'Carriers'.
call method excel_spreadsheet_manager->copy_table_to_excel_worksheet
exporting
source_stack = booking_stack
source_description = 'Bookings'.
catch zcx_excel ##NO_HANDLER.
endtry.
call method excel_spreadsheet_manager->send_excel_via_email exporting recipient = recipient.
endmethod.
endclass.
class email_address_resolver definition
final
create private.
public section.
class-data : singleton type ref to email_address_resolver read-only.
class-methods: class_constructor.
methods : resolve_email_address
importing
userid
type syuname
exporting
email_address
type data_exchangeable=>email_recipient
.
endclass.
class email_address_resolver implementation.
method class_constructor.
create object email_address_resolver=>singleton.
endmethod.
method resolve_email_address.
select single smtp_addr
into email_address
from adr6 ##WARN_OK
inner join
usr21 on usr21~persnumber eq adr6~persnumber
where usr21~bname eq userid.
endmethod.
endclass.
parameters : rowcount type data_exchangeable=>row_counter.
parameters : recipien type data_exchangeable=>email_recipient.
initialization.
call method email_address_resolver=>singleton->resolve_email_address
exporting
userid = sy-uname
importing
email_address = recipien.
start-of-selection.
call method process_driver=>singleton->drive_process
exporting
row_count = rowcount
recipient = recipien.

We no longer have any static classes remaining in the program. The entire program is now in conformance with Rule 5.3 and will produce the same results as the version of the program before we began implementing these changes.

What’s next?

This concludes the 6-part series Getting comfortable using the Object-Oriented design model with ABAP, however there remain other concepts of object-oriented programming not covered in this series. If you are interested in pursuing this further, I can suggest obtaining the example programs and corresponding requirements document freely available for download at:

https://www.apress.com/us/book/9781484228371

In this case the starting program begins as an even simpler procedurally-designed ABAP program and similarly gets transformed into comparable local OO classes and interfaces, but there are far more aspects of object-oriented design to be explored as the transformation of this starting program reflects the concepts presented in the associated book, taking the reader through the entire gamut of object-oriented design concepts and design patterns as they apply to ABAP programming.

 

 
4 Comments
BaerbelWinkler
Active Contributor
Thanks for this blog-series James, which I just finished working through. I really appreciate the effort you spent on putting this together!

I'm however afraid that I still have more "why" and "what's the point" questions than I most likely should have by now. But, that's just the way it is and is a sign of me still having a hard time with coming to grips with ABAP OO.

So, here is my rough and ready list of questions and comments in no particular order:

  • While following the steps in part 6, I forgot to e.g. switch "CLASS-METHODS: resolve_email_address" to "METHODS: resolve_email_address" in CLASS email_address_resolver DEFINITION
    ==> Why isn't this producing any syntax-errors and still works as before?

  • Why is quite some effort expanded to get rid of static classes in a program working with local class definitions where I know that each is just called once as "I" control the process flow via the main processing logic? I feel that this just adds unnecessary complexity and makes the code a lot more difficult to read and write. I get, that at least some of this is due to this being a constructed example program to demonstrate various aspects of ABAP OO, but for me it's not making it easier to take the leap and really embrace it.

  • Even though we somehow got rid of some static classes we replaced them with a CLASS-METHOD class_constructor which seems to basically just be a very special type of static class used/invoked comparably to the event INITIALIZATION in a procedural report program. Is this really worth the trouble?

  • Even though you provide reasons for preferring the name "singleton" in the class-data definition, I'm wondering if it might not be easier to understand if it were "prefixed" with the class-name itself, so that it's e.g. flight_singleton, carrier_singleton and booking_singleton respectively? With the many "singletons" now used in the code I find it hard to quickly tell what I'm looking at. Not to mention that "singleton" in and of itself is a rather abstract term which I find hard to really relate to.

  • How would one actually go about constructing such a program bottom-up (i.e. from scratch) instead of top-down like was done here with working from a procedural program as the starting point? I'd like to try but am struggling to see how to perhaps get a simple template program created which I could then use as the starting point for new programs.


That's it for the moment - I'm sure I'll have more questions once I've mulled this over a bit more.

Thanks again and Cheers

Baerbel
Bärbel,

You have asked some very good questions which I will try my best to answer.

Question: While following the steps in part 6, I forgot to e.g. switch “CLASS-METHODS: resolve_email_address” to “METHODS: resolve_email_address” in CLASS email_address_resolver DEFINITION ==> Why isn’t this producing any syntax-errors and still works as before?

Answer: Class "email_address_resolver" is a good example of a class that has no attributes. It has only a single public method "resolve_email_address" that can be explicitly invoked by an external caller.  Accordingly, despite the fact that you were able to create an instance of the class when this method was still marked as a static method, the corresponding instance had no instance attributes or instance methods that could be accessed. However, there is neither a syntax violation nor a failure of the call because the static method "resolve_email_address" contains no statements referencing any instance attributes or instance methods. Had that been the case, then a syntax violation would have appeared making you realize you had forgot to change its definition from static method to instance method. I have succumbed to this very same oversight more than once.

Question: Why is quite some effort expanded to get rid of static classes in a program working with local class definitions where I know that each is just called once as “I” control the process flow via the main processing logic? I feel that this just adds unnecessary complexity and makes the code a lot more difficult to read and write. I get, that at least some of this is due to this being a constructed example program to demonstrate various aspects of ABAP OO, but for me it’s not making it easier to take the leap and really embrace it.

Answer: Much of the power of using the OO model becomes evident during maintenance cycles. Yes, while you are designing the program you know, as you say, the control flow you have implemented and that you are instantiating classes, whether local or global, only one time. Later, the next developer might look at the code and not be able to determine that information so quickly if its first version is written using shortcuts. What might appear today as unnecessary complexity often pays dividends later when changes are required.

Question: Even though we somehow got rid of some static classes we replaced them with a CLASS-METHOD class_constructor which seems to basically just be a very special type of static class used/invoked comparably to the event INITIALIZATION in a procedural report program. Is this really worth the trouble?

Answer: Yes, the static constructor (class_constructor) is a very special type of static method invoked only when the class is loaded into storage. It must be defined with this reserved name and it cannot be explicitly invoked by any ABAP statements. I consider the static constructor to have more similarities with the classic ABAP event block LOAD-OF-PROGRAM than with event block INITIALIZATION. With LOAD-OF-PROGRAM you get a chance to do things once and only once as the program is loaded into storage. I suppose it is a judgment call to decide whether it is worth using. I have found that classes invoked from the classic ABAP event blocks will not require a corresponding global variable to access the singleton having a public static attribute reference instantiated by the static constructor. I consider the absence of global variables to be worth the effort.

Question: Even though you provide reasons for preferring the name “singleton” in the class-data definition, I’m wondering if it might not be easier to understand if it were “prefixed” with the class-name itself, so that it’s e.g. flight_singleton, carrier_singleton and booking_singleton respectively? With the many “singletons” now used in the code I find it hard to quickly tell what I’m looking at. Not to mention that “singleton” in and of itself is a rather abstract term which I find hard to really relate to.

Answer: For access to it, the public static attribute “singleton” would require a class name and class selector to precede it, such as “call method flight_report=>singleton->present_report”, which essentially provides the same type of qualification to identify which singleton is being accessed, providing the qualification not in the name of the attribute itself but in the access to it.

Question: How would one actually go about constructing such a program bottom-up (i.e. from scratch) instead of top-down like was done here with working from a procedural program as the starting point? I’d like to try but am struggling to see how to perhaps get a simple template program created which I could then use as the starting point for new programs.

Answer: For new programs I need to write I have a syntactically correct template containing one local singleton class “process_driver”, having a single empty instance method “drive_process”, invoked by a “call method process_driver=>singleton->drive_process” statement in the start-of-selection classic ABAP event block. This enables me to use the OO model to write new programs and even small throw-away programs with initial selection screens to test the nuances of how ABAP statements behave. From here I add new class definitions as required. Usually I determine ahead of time the real-life entities I will need for the required processing and these become my new classes used by process_driver. I often change the name of the class from “process_driver” to something more applicable to the task. I don't always get it right the first time, and find myself reorganizing the components of the program as development progresses.

 

I am happy that you have been diligent in pursuing this new design model in spite of the difficulties it has presented. But don't despair. You are doing well and raising some of the very same points I raised when I was first grappling with the OO design model. You boldly have taken the first steps, but it will take some time and practice to become more comfortable with it. Eventually you might find that thinking of software design using the OO model becomes second-nature, as comfortable and familiar to you as the procedural model is for you today.

Regards,

Jim
BaerbelWinkler
Active Contributor
0 Kudos
Thanks for your response to my questions, James!

One of my main issues with the OO-concept in general is, that at least some of the rules don't really have easy to grasp answers to "why should it be done like this?" questions. This in turn makes it difficult for me to relate to and remember them as I'm always wondering "why?" and "what's the point?".
seso
Advisor
Advisor
0 Kudos
I just want to add: If you use the singleton directly like cl_class=>singleton->method( ), you gain more or less nothing compared to having a static method call.

The real benefits come, when you separate object creation (or referencing) from using the object.

E.g. you could receive a reference to the singleton in the CONSTRUCTOR of your class and store it in a member variable. In your actual code you then call the method on the member variable.

This has following benefits:

  • The dependencies of your class are directly visible and explicit

  • The member can be exchanged by a testdouble when running unit tests -> dependency injection

Labels in this area