Skip to Content

This blog represents part 4 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 duplication of code amongst three report classes and consolidated that duplicated code, making it available to the three classes through the use of Inheritance. 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 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
                                       abstract.
  public section.
    methods      : produce_report abstract
                     importing
                       row_count
                         type data_exchangeable=>row_counter
                       excel_spreadsheet_manager
                         type ref to excel_spreadsheet_manager
                     raising
                       zcx_excel
                 .
  protected 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 flight_report                    definition
                                       final
                                       inheriting from report.
  public section.
    methods      : produce_report redefinition
                 .
endclass.
class flight_report                    implementation.
  method produce_report.
    data         : flight_stack   type standard table of sflight
                 .
    select *
      into table flight_stack
      from sflight
             up to row_count rows.
    call method me->present_report
      changing
        record_stack              = flight_stack.
    call method excel_spreadsheet_manager->copy_table_to_excel_worksheet
      exporting
        source_stack              = flight_stack
        source_description        = 'Flights'.
  endmethod.
endclass.
class carrier_report                   definition
                                       final
                                       inheriting from report.
  public section.
    methods      : produce_report redefinition
                 .
endclass.
class carrier_report                   implementation.
  method produce_report.
    data         : carrier_stack  type standard table of scarr
                 .
    select *
      into table carrier_stack
      from scarr
             up to row_count rows.
    call method me->present_report
      changing
        record_stack              = carrier_stack.
    call method excel_spreadsheet_manager->copy_table_to_excel_worksheet
      exporting
        source_stack              = carrier_stack
        source_description        = 'Carriers'.
  endmethod.
endclass.
class booking_report                   definition
                                       final
                                       inheriting from report.
  public section.
    methods      : produce_report redefinition
                 .
endclass.
class booking_report                   implementation.
  method produce_report.
    data         : booking_stack  type standard table of sbook
                 .
    select *
      into table booking_stack
      from sbook
             up to row_count rows.
    call method me->present_report
      changing
        record_stack              = booking_stack.
    call method excel_spreadsheet_manager->copy_table_to_excel_worksheet
      exporting
        source_stack              = booking_stack
        source_description        = 'Bookings'.
  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         : flight_report  type ref to flight_report
                 , carrier_report type ref to carrier_report
                 , booking_report type ref to booking_report
                 , excel_spreadsheet_manager
                                  type ref to excel_spreadsheet_manager
                 .
    create object: flight_report
                 , carrier_report
                 , booking_report
                 , excel_spreadsheet_manager
                 .
    try.
      call method flight_report->produce_report exporting row_count = row_count
                                                          excel_spreadsheet_manager = excel_spreadsheet_manager.
      call method carrier_report->produce_report exporting row_count = row_count
                                                          excel_spreadsheet_manager = excel_spreadsheet_manager.
      call method booking_report->produce_report exporting row_count = row_count
                                                          excel_spreadsheet_manager = excel_spreadsheet_manager.
    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 classes having multiple responsibilities and transform them into classes that adhere to the Single Responsibility Principle. 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

It’s one thing to understand the object-oriented design principles Encapsulation, Abstraction and Inheritance, and to use them effectively in our programming efforts as we’ve done so far with our example program. It is quite another to learn the lessons of software maintenance that have been discovered by pioneers of object-oriented programming. One of these pioneers is Robert C. Martin (https://en.wikipedia.org/wiki/Robert_C._Martin), whose experience with software maintenance led him to define the SOLID principles (https://en.wikipedia.org/wiki/SOLID), which are:

  • Single Responsibility Principle
  • Open-Closed Principle
  • Liskov Substitution Principle
  • Interface Segregation Principle
  • Dependency Inversion Principle

Known affectionately in the software industry as Uncle Bob, he defined the SOLID principles in his 2000 paper titled Design Principles and Design Patterns (http://www.cvc.uab.es/shared/teach/a21291/temes/object_oriented_design/materials_adicionals/principles_and_patterns.pdf). These are known as the SOLID principles due to the first letter of each principle building the acronym SOLID. The SOLID principles have been embraced by many scholars of object-oriented design as good advice for keeping object-oriented software flexible and maintainable. For now, we are going to focus on the first of these principles: the Single Responsibility Principle.

Single Responsibility Principle

According to the Principles of Object-Oriented Design (http://www.butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod), the Single Responsibility Principle states:

  • A class should have one, and only one, reason to change.

Currently our flight_report, carrier_report and booking_report classes all perform similar processing:

  • Get from its respective persistence repository the number of records indicated by the user
  • Display these records in an ALV report
  • Arrange for these records to be captured in a worksheet of an Excel spreadsheet

As you can see, these report classes do more than just report – each class has the responsibility for each of the three bullet points noted above, only the second of which involves anything related to reporting. Accordingly, these classes run afoul of the Single Responsibility Principle – each one is doing more than its class name suggests it should have the responsibility to do.

Eliminating multiple responsibilities – step 1

Let’s change the program so that these classes have only a single responsibility. The first thing we’ll do is to remove from these classes the responsibility to retrieve records to be presented in the report. Define a new static class called flight_records_retriever using the skeleton class shown below, placing this new class immediately after the endinterface statement.

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.
  endmethod.
endclass.

At this point a syntax check should pass. Now make the following changes:

  • Move the select statement from method produce_report of class flight_report into the new retrieve records statement, replacing actual parameter flight_stack with record_stack.
  • Replace the removed lines from method produce_report with a call to this new method, as in:
    call method flight_records_retriever=>retrieve_records
      exporting
        row_count                 = row_count
      importing
        record_stack              = flight_stack.

At this point a syntax check should pass. This change does not completely resolve the problem of the flight_report still being responsible for retrieving the records it presents in its report, but this is the first step in reaching that goal.

Next, make the same relative changes to classes carrier_report and booking_report as were made for class flight_report, including the creation of two new respective classes called carrier_records_retriever and booking_records_retriever, placing each new class ahead of class excel_spreadsheet_manager.

At this point a syntax check should pass. Executing the program at this point should prove that it still works as before.

Eliminating multiple responsibilities – step 2

Next, change the signature of method produce_report of class report so that:

  • It no longer accepts importing parameter row_count.
  • It now accepts a changing parameter named record_stack, defined as type standard table.

After applying these changes the definition of the method should look like this:

    methods      : produce_report abstract
                     importing
                       excel_spreadsheet_manager
                         type ref to excel_spreadsheet_manager
                     changing
                       record_stack
                         type standard table
                     raising
                       zcx_excel
                 .

A syntax check will fail now in method produce_report of class flight_report due to the reference to parameter row_count, which no longer is defined in the signature of this method. Change this method in the following ways:

  • Move the data statement to precede the create object statement in method drive_process of class process_driver, changing its type to indicate not standard table of sflight but instead type flight_records_retriever=>record_list.
  • Move the call to method retrieve_records of class flight_records_retriever to follow the create object statement in method drive_process of class process_driver.
  • For the formal parameter record_stack used with the call to method present_report, change the actual parameter from flight_stack to record_stack.
  • For the formal parameter source_stack used with the call to method copy_table_to_excel_worksheet of class excel_spreadsheet_manager, change the actual parameter from flight_stack to record_stack.

Afterward, the code in method produce_report of class flight_report should look like this:

  method produce_report.
    call method me->present_report
      changing
        record_stack              = record_stack.
    call method excel_spreadsheet_manager->copy_table_to_excel_worksheet
      exporting
        source_stack              = record_stack
        source_description        = 'Flights'.
  endmethod.

Next, make the same relative changes to classes carrier_report and booking_report as were made for class flight_report.

Then in method drive_process of class process_driver, change the three call method statements following the try statement so that each one:

  • no longer has an exporting parameter for row_count
  • has a changing parameter for formal parameter record_stack whose actual parameter is flight_stack, carrier_stack and booking_stack, respectively

When completed, method drive_process should look like this:

  method drive_process.
    data         : flight_report  type ref to flight_report
                 , carrier_report type ref to carrier_report
                 , booking_report type ref to booking_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: flight_report
                 , carrier_report
                 , booking_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.
    try.
      call method flight_report->produce_report exporting excel_spreadsheet_manager = excel_spreadsheet_manager
                                                 changing record_stack = flight_stack.
      call method carrier_report->produce_report exporting excel_spreadsheet_manager = excel_spreadsheet_manager
                                                  changing record_stack = carrier_stack.
      call method booking_report->produce_report exporting excel_spreadsheet_manager = excel_spreadsheet_manager
                                                  changing record_stack = booking_stack.
    catch zcx_excel ##NO_HANDLER.
    endtry.
    call method excel_spreadsheet_manager->send_excel_via_email exporting recipient = recipient.
  endmethod.

At this point a syntax check will pass. Now none of the produce_report methods of classes flight_report, carrier_report and booking_report remain responsible for retrieving the records each one presents in its corresponding ALV report. Instead, method drive_process of class process_driver now assumes the responsibility for retrieving these records and passing them to the produce_report methods.

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

Eliminating multiple responsibilities – step 3

Whereas our produce_report methods no longer retrieve the records they present in the ALV report they produce, these methods still retain the responsibility for sending those records to be converted to a worksheet of an Excel spreadsheet, so let’s also make these corrections.

First, change the signature of method produce_report in class report so that it no longer imports an instance of an excel_spreadsheet_manager and no longer indicates that it can raise exception zcx_excel.

At this point the syntax check fails in method produce_report of class flight_report on the call to method copy_table_to_excel_worksheet. Move this call method statement from method produce_report of class flight_report into method drive_process of class process_driver, placing it immediately before the catch clause of the try-endtry block and changing its actual parameter from record_stack to flight_stack.

Next, make the same relative changes to classes carrier_report and booking_report as were made for class flight_report.

Finally, in method drive_process of class process_driver, remove the exporting parameters from the three calls to the produce_report methods.

At this point a syntax check passes, but the three calls to method produce_report within the try-entry block of method driver_process no longer can raise the exception zcx_excel, so these statements should be moved ahead of the corresponding try statement. When completed, method drive_process should look like this:

  method drive_process.
    data         : flight_report  type ref to flight_report
                 , carrier_report type ref to carrier_report
                 , booking_report type ref to booking_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: flight_report
                 , carrier_report
                 , booking_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 flight_report->produce_report changing record_stack = flight_stack.
    call method carrier_report->produce_report changing record_stack = carrier_stack.
    call method booking_report->produce_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.

Now none of the produce_report methods of classes flight_report, carrier_report and booking_report remain responsible for insuring that the records it uses to create the ALV report also are copied to an Excel worksheet. Instead, method drive_process of class process_driver now assumes this responsibility.

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
                                       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
                                       abstract.
  public section.
    methods      : produce_report abstract
                     changing
                       record_stack
                         type standard table
                 .
  protected 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 flight_report                    definition
                                       final
                                       inheriting from report.
  public section.
    methods      : produce_report redefinition
                 .
endclass.
class flight_report                    implementation.
  method produce_report.
    call method me->present_report
      changing
        record_stack              = record_stack.
  endmethod.
endclass.
class carrier_report                   definition
                                       final
                                       inheriting from report.
  public section.
    methods      : produce_report redefinition
                 .
endclass.
class carrier_report                   implementation.
  method produce_report.
    call method me->present_report
      changing
        record_stack              = record_stack.
  endmethod.
endclass.
class booking_report                   definition
                                       final
                                       inheriting from report.
  public section.
    methods      : produce_report redefinition
                 .
endclass.
class booking_report                   implementation.
  method produce_report.
    call method me->present_report
      changing
        record_stack              = record_stack.
  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         : flight_report  type ref to flight_report
                 , carrier_report type ref to carrier_report
                 , booking_report type ref to booking_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: flight_report
                 , carrier_report
                 , booking_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 flight_report->produce_report changing record_stack = flight_stack.
    call method carrier_report->produce_report changing record_stack = carrier_stack.
    call method booking_report->produce_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.

We’ve changed the program so that none of the produce_report methods of classes flight_report, carrier_report and booking_report remain responsible for retrieving the records it uses to create the ALV report and none of these classes remain responsible for insuring that the records it uses to create the ALV report also are copied to an Excel worksheet. In short, the three classes flight_report, carrier_report and booking_report no longer have any responsibilities beyond producing a report, all three now complying with the Single Responsibility Principle.

What’s next?

There remain other concepts of object-oriented programming for us to explore using this simple example program. After removing the multiple responsibilities from the classes flight_report, carrier_report and booking_report, we now notice that all of these classes are identical to each other with the exception of their names. The next step is to refactor the program further to remove these duplicative classes. This will be covered in the blog representing part 5 of this 6-part series Getting comfortable using the Object-Oriented design model with ABAP.

To report this post you need to login first.

7 Comments

You must be Logged on to comment or reply to a post.

  1. Hugo de Groot

    Hello James,

    I like this blog series! It helps me to deepen my understanding of the usage of OO patterns in ABAP. I’ve also added your book to my Kindle, it’s on my reading list.

    One request, could you please update your blogs in this blog-series and add hyperlinks to the related blogs (Part 1, 2, etc.) That would be convenient. Thank you!

    —Hugo

     

    (0) 
    1. James E. McDonough Post author

      Hugo,

      I am happy to hear that this is helpful to you.

      It is my intent to update all 6 of these series blogs with the links to each other once I have published the last one.

      Jim

       

      (0) 
  2. Bärbel Winkler

    Thanks yet again, James, for going to all this trouble to spell things out step by step for folks like me! I just wish that I could already tell you that “yes, I’m getting more comfortable with ABAP OO”! As of right now, I’m unfortunately still in a “more confused” than “more comfortable” state, some of which might just have to do with the fact, that it’s actually too warm (for me) to do much more than trying to follow your instructions.

    I’m kind of losing track of what is done where and in which sequence things eventually get executed. And I have to admit that I still find this a lot easier to see in a properly written and formatted procedural program where I can put the form-routines basically where I want following the overall process flow! To somewhat alleviate this issue, I executed the latest state of the program via SAT without aggregation to get the call hierarchy which I then put into Excel and color-coded a bit (yes, I’m visually inclined!). That way, I at least now understand the process flow better:

    One suggestion I have for potentially similar posts in the future: how about using UPPER CASE for all the method- and class-names in the instructions? It may just be me, but I think it would be easier to follow along when the text looks e.g. like this for a random example:

    “First, change the signature of method PRODUCE_REPORT in class REPORT so that it no longer imports an instance of an EXCEL_SPREADSHEET_MANAGER and no longer indicates that it can raise exception ZCX_EXCEL.

    At this point the syntax check fails in method PRODUCE_REPORT of class FLIGHT_REPORT on the call to method COPY_TABLE_TO_EXCEL_WORKSHEET. Move this call method statement from method PRODUCE_REPORT of class FLIGHT_REPORT into method DRIVE_PROCESS of class PROCESS_DRIVER, placing it immediately before the catch clause of the try-endtry block and changing its actual parameter from RECORD_STACK to FLIGHT_STACK.”

    Now, off to tackle part 5 ….

    Cheers

    Baerbel

    (0) 
      1. Bärbel Winkler

        Thanks, Jacques! I wasn’t aware of this SAT-feature (learned something new already today!). Unfortunately, when I try to just use what the transaction itself offers in our NW 7.40 with EHP8 system, I get errors apparently related to something or other with JAVA (no clue what exactly). So, if that already causes errors, I take it that this will render any efforts to implement your suggested enhancement rather futile? I also wouldn’t be able to apply this in anything apart from a sandbox system and there I get an error that an expected website cannot be displayed (possibly due to the way the sandbox is configured, but not sure).

        Cheers

        Bärbel

         

        (0) 
        1. Jacques Nomssi

          Hello Bärbel,

          you can make it work anyway.

          • The enhancement uses PlantUML, you don’t need a local Java OCX.
          • If your Sanbox is not connected to the internet, you can save the result in a text file and go to the PlantUML online server to create the graphics (manual step).

          JNN

          (0) 

Leave a Reply