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: 
5,716
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
Labels in this area