Implementing Factory Methods

Objective

After completing this lesson, you will be able to Use factory methods.

Factory Methods

Sometimes a class needs to keep control of its instances. This could be to prevent multiple instances being created. In order to do so, it must prevent its users from creating instances themselves. You do this in ABAP by using the CREATE PRIVATE addition in the CLASS DEFINITION statement. Any attempt to use the NEW operator outside the class will now lead to a syntax error. Note that other programming languages achieve this effect by altering the visibility of the constructor method. ABAP does not support this.

Since users of the class cannot create their own instances, the class must then provide a public static method that creates instances and returns them to the user. This is called a factory method.

Try It Out: Factory Methods Implementation

  1. Create a new class that implements the interface IF_OO_ADT_CLASSRUN.
  2. Switch to the Local Types tab and copy the following code snippet into the editor:
    Code Snippet
    Copy code
    Switch to dark mode
    12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455
    CLASS lcl_connection DEFINITION CREATE PRIVATE. PUBLIC SECTION. METHODS constructor IMPORTING airlineid TYPE /dmo/carrier_id connectionnumber TYPE /dmo/connection_id fromAirport TYPE /dmo/airport_from_id toAirport TYPE /dmo/airport_to_id. CLASS-METHODS get_connection IMPORTING airlineId TYPE /dmo/carrier_id connectionNumber TYPE /dmo/connection_id RETURNING VALUE(ro_connection) TYPE REF TO lcl_Connection. PRIVATE SECTION. DATA AirlineId TYPE /dmo/carrier_id. DATA ConnectionNumber TYPE /dmo/connection_id. DATA fromAirport TYPE /dmo/airport_from_id. DATA toAirport TYPE /dmo/airport_to_id. ENDCLASS. CLASS lcl_connection IMPLEMENTATION. METHOD constructor. me->airlineid = airlineid. me->connectionnumber = connectionnumber. me->fromAirport = fromAirport. me->toAirport = toAirport. ENDMETHOD. METHOD get_connection. DATA fromAirport TYPE /dmo/airport_from_id. DATA toAirport TYPE /dmo/airport_to_id. SELECT SINGLE FROM /dmo/connection FIELDS airport_from_id, airport_to_id WHERE carrier_id = @airlineid AND connection_id = @connectionnumber INTO ( @fromAirport, @toAirport ). ro_connection = NEW #( airlineid = airlineid connectionnumber = connectionnumber fromairport = fromairport toairport = toairport ). ENDMETHOD. ENDCLASS.
  3. Switch to the Global Class tab and paste the following code snippet into the implementation of method if_oo_adt_classrun~main( ):
    Code Snippet
    Copy code
    Switch to dark mode
    12345678910111213141516171819202122232425262728293031323334
    CLASS zcl_s4d401_factory DEFINITION PUBLIC FINAL CREATE PUBLIC . PUBLIC SECTION. interfaces if_oo_adt_classrun. PROTECTED SECTION. PRIVATE SECTION. ENDCLASS. CLASS zcl_s4d401_factory IMPLEMENTATION. METHOD if_oo_adt_classrun~main. data connection type ref to lcl_connection. * Debug the method to show that the class returns objects, but that there are different * objects for the same combination of airline and flight number connection = lcl_connection=>get_connection( airlineid = 'LH' connectionnumber = '0400' ). connection = lcl_connection=>get_connection( airlineid = 'LH' connectionnumber = '0400' ). ENDMETHOD. ENDCLASS.
  4. Activate the class with Ctrl + F3.
  5. Play around with the coding. In particular, debug the main() method and note the internal instance numbers assigned to the objects returned by the factory method.

Singleton Patterns

The purpose of the singleton pattern is to ensure that there is only one instance of a class in an application. This could be a single instance of the class or, in our case, a single instance for each combination of key attributes.

Watch the following videos to know more about the Singleton Pattern.

Try It Out: Singleton Pattern Implementation

  1. Create a new class that implements the interface IF_OO_ADT_CLASSRUN.
  2. Switch to the Local Types tab and copy the following code snippet into the editor:
    Code Snippet
    Copy code
    Switch to dark mode
    1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
    CLASS lcl_connection DEFINITION CREATE PRIVATE. PUBLIC SECTION. METHODS constructor IMPORTING airlineid TYPE /dmo/carrier_id connectionnumber TYPE /dmo/connection_id fromAirport TYPE /dmo/airport_from_id toAirport TYPE /dmo/airport_to_id. CLASS-METHODS get_connection IMPORTING airlineId TYPE /dmo/carrier_id connectionNumber TYPE /dmo/connection_id RETURNING VALUE(ro_connection) TYPE REF TO lcl_Connection. PRIVATE SECTION. TYPES: BEGIN OF ts_instance, airlineId TYPE /dmo/carrier_id, connectionNumber TYPE /dmo/connection_id, object TYPE REF TO lcl_connection, END OF ts_instance, tt_instances TYPE HASHED TABLE OF ts_instance WITH UNIQUE KEY airlineId ConnectionNumber. DATA AirlineId TYPE /dmo/carrier_id. DATA ConnectionNumber TYPE /dmo/connection_id. DATA fromAirport TYPE /dmo/airport_from_id. DATA toAirport TYPE /dmo/airport_to_id. CLASS-DATA connections TYPE tt_instances. ENDCLASS. CLASS lcl_connection IMPLEMENTATION. METHOD constructor. me->airlineid = airlineid. me->connectionnumber = connectionnumber. me->fromAirport = fromAirport. me->toAirport = toAirport. ENDMETHOD. METHOD get_connection. DATA fromAirport TYPE /dmo/airport_from_id. DATA toAirport TYPE /dmo/airport_to_id. IF NOT line_exists( connections[ airlineid = airlineid connectionnumber = connectionnumber ] ). SELECT SINGLE FROM /dmo/connection FIELDS airport_from_id, airport_to_id WHERE carrier_id = @airlineid AND connection_id = @connectionnumber INTO ( @fromAirport, @toAirport ). ro_connection = NEW #( airlineid = airlineid connectionnumber = connectionnumber fromairport = fromairport toairport = toairport ). DATA(new_instance) = VALUE ts_instance( airlineId = airlineId connectionnumber = connectionnumber object = ro_connection ). INSERT new_instance INTO TABLE connections. ELSE. ro_connection = connections[ airlineId = airlineId connectionnumber = connectionnumber ]-object. ENDIF. ENDMETHOD. ENDCLASS.
  3. Switch to the Global Class tab and paste the following code snippet into the implementation of method if_oo_adt_classrun~main( ):
    Code Snippet
    Copy code
    Switch to dark mode
    123456789101112131415161718192021222324252627282930313233
    CLASS zcl_s4d401_factory_singleton DEFINITION PUBLIC FINAL CREATE PUBLIC . PUBLIC SECTION. interfaces if_oo_adt_classrun. PROTECTED SECTION. PRIVATE SECTION. ENDCLASS. CLASS zcl_s4d401_factory_singleton IMPLEMENTATION. METHOD if_oo_adt_classrun~main. data connection type ref to lcl_connection. * Debug the method to show that the class always returns the same object * for the same combination of airline and flight number connection = lcl_connection=>get_connection( airlineid = 'LH' connectionnumber = '0400' ). connection = lcl_connection=>get_connection( airlineid = 'LH' connectionnumber = '0400' ). ENDMETHOD. ENDCLASS.
  4. Activate the class with Ctrl + F3.
  5. Play around with the coding. In particular, debug the main() method and note the internal instance numbers assigned to the objects returned by the factory method.

Implement a Factory Method

You realize that, as it is now, your code allows the existence of several instances of LCL_CARRIER that represent the same airline. To amend this, you define and implement an factory method that ensures that only one instance is created for a given carrier ID value.

Template:

  • /LRN/CL_S4D401_OOS_INTF_USAGE (Global Class)

Solution:

  • /LRN/CL_S4D401_OOS_FACTORY (Global Class)

Task 1: Copy Template (Optional)

Copy the template class. If you finished the previous exercise, you can skip this task and continue editing your class ZCL_##_SOLUTION.

Steps

  1. Copy class /LRN/CL_S4D401_OOS_INTF_USAGE to a class in your own package (suggested name: ZCL_##_SOLUTION, where ## stands for your group number).

    1. In the Project Explorer view, right-click class /LRN/CL_S4D401_OOS_INTF_USAGE to open the context menu.

    2. From the context menu, choose Duplicate ....

    3. Enter the name of your package in the Package field. In the Name field, enter the name ZCL_##_SOLUTION, where ## stands for your group number.

    4. Adjust the description and choose Next.

    5. Confirm the transport request and choose Finish.

  2. Activate the copy.

    1. Press Ctrl + F3 to activate the class.

Task 2: Define a Factory Method

Use a quick fix to define a factory method for local class LCL_CARRIER (suggested name: GET_INSTANCE). In the factory method, perform the existence check and the authority check before you actually create an instance. Then restrict the instance creation for this class to enforce the usage of the factory method.

Steps

  1. In local class LCL_CARRIER, use a quick fix to define a factory method.

    Hint

    Constructor parameters are added to the signature of the factory method by default. It is not necessary to explicitly select the carrier_id attribute as an attribute to be initialized.
    1. Navigate to the definition of LCL_CARRIER.

    2. In the CLASS … DEFINITION statement, place the cursor on lcl_carrier and press Ctrl + 1 to invoke the quick fix.

    3. From the list of available quick fixes, choose Generate factory method create.

    4. In field Name, enter GET_INSTANCE.

    5. In the Select attributes to initialize: section, choose Delesect All, then choose Finish.

    6. The quick fix should have added the following definition:

      Code Snippet
      Copy code
      Switch to dark mode
      1234567
      CLASS-METHODS get_instance IMPORTING i_carrier_id TYPE /dmo/carrier_id RETURNING value(r_result) TYPE REF TO lcl_carrier.

      and the following implementation:

      Code Snippet
      Copy code
      Switch to dark mode
      123456789
      METHOD get_instance. r_result = NEW #( i_carrier_id = i_carrier_id ). ENDMETHOD.
  2. Move the SELECT statement and the AUTHORITY-CHECK statement from the CONSTRUCTOR to the implementation of the factory method. Make sure your perform the checks before the instantiation.

    1. Navigate to the implementation of the CONSTRUCTOR method of local class LCL_CARRIER.

    2. Select the SELECT statement and the AUTHORITY-CHECK statement, including the evaluation of system field SY-SUBRC and the exception raising, and press Ctrl + C to copy the code snippet to the clipboard.

    3. Press Ctrl + < to add a comment sign in front of each select code row.

    4. Navigate to the implementation of method GET_INSTANCE, place the cursor before the value assignment for parameter r_result and press Ctrl + V to insert the code snippet.

    5. The code should now look like this:

      Code Snippet
      Copy code
      Switch to dark mode
      123456789
      METHOD get_instance. r_result = NEW #( i_carrier_id = i_carrier_id ). ENDMETHOD.
  3. In the SELECT statement, adjust the INTO clause. Store the result in an inline declared structure (suggested name: details) instead of instance attributes name and currency_code.

    1. Adjust the code as follows:

      Code Snippet
      Copy code
      Switch to dark mode
      1234567
      SELECT SINGLE FROM /lrn/carrier FIELDS concat_with_space( carrier_id, name, 1 ) , currency_code WHERE carrier_id = @i_carrier_id * INTO ( @me->name, @me->currency_code ).
  4. After the instantiation, assign the values from structure details to the instance attributes of the new instance.

    1. Adjust the code as follows:

      Code Snippet
      Copy code
      Switch to dark mode
      123456
      r_result = NEW #( i_carrier_id = i_carrier_id ). ENDMETHOD.
  5. Use quick fixes to add raising declarations for the two exceptions.

    1. Choose the warning icon with the light bulb on the left-hand side of either RAISE EXCEPTION statement to invoke the quick fix.

    2. From the list of available quick fixes, choose Add raising declaration.

    3. Repeat for the other RAISING EXCEPTION statement.

    4. The quick fixes should have adjusted the code as follows:

      Code Snippet
      Copy code
      Switch to dark mode
      12345678
      CLASS-METHODS get_instance IMPORTING i_carrier_id TYPE /dmo/carrier_id RETURNING value(r_result) TYPE REF TO lcl_carrier .
  6. Restrict the instantiation of local class LCL_CARRIER. Make sure instantiation is only allowed within the class itself.

    1. Navigate to the definition of LCL_CARRIER.

    2. Adjust the code as follows:

      Code Snippet
      Copy code
      Switch to dark mode
      123
      CLASS lcl_carrier DEFINITION .
  7. Move the definition of the CONSTRUCTOR method to the private section.

    Note

    This is optional, but should be done to improve readability.
    1. Copy and paste the METHODS statement for method CONSTRUCTOR from the public section to the private section.

  8. In the IF_OO_ADT_CLASSRUN~MAIN method of the global class, replace the NEW expression with a call of the factory method GET_INSTANCE.

    1. Adjust the code as follows:

      Code Snippet
      Copy code
      Switch to dark mode
      12
      * DATA(carrier) = NEW lcl_carrier( i_carrier_id = c_carrier_id ).
  9. In the CLASS_SETUP method of the local test class LTCL_FIND_FLIGHTS, replace the NEW expression with a call of the factory method GET_INSTANCE.

    1. Adjust the code as follows:

      Code Snippet
      Copy code
      Switch to dark mode
      12
      * DATA(carrier) = NEW lcl_carrier( i_carrier_id = c_carrier_id ).
  10. Activate and test your global class as console app. Change the value of constant c_carrier_id and debug the execution of the GET_INSTANCE method.

    1. Press Ctrl + F3.

    2. Press F9.

Task 3: Extend the Factory Method

In local class LCL_CARRIER, define a static attribute in which you can store references to the instances of LCL_CARRIER (Suggested name: instances). Extend the implementation of the factory method. After the creation of an instance, store a reference to the new instance in attribute instances. Only create a new instance if instances does not yet contain the reference to an instance for the requested value of carrier ID.

Steps

  1. In local class LCL_CARRIER, use a quick fix to create a table type for this class (suggested name: tt_carriers).

    1. Navigate to the definition of local class LC_CARRIER.

    2. In the CLASS … DEFINITION statement, place the cursor on lcl_carrier and press Ctrl + 1 to invoke the quick fix.

    3. From the list of available quick fixes, choose Generate table type for lcl_carrier.

    4. In the new TYPES statement, replace tab with tt_carriers.

    5. The TYPES statement should now look like this:

      Code snippet
      Expand
  2. Define a private static attribute based on that table type (suggested name: instances.

    1. Scroll down to the PRIVATE SECTION statement.

    2. Adjust the code as follows:

      Code Snippet
      Copy code
      Switch to dark mode
      123
      PRIVATE SECTION.
  3. Adjust the implementation of the factory method GET_INSTANCE. After the instantiation, add a reference to the new instance to static attribute instances.

    1. Adjust the code as follows:

      Code Snippet
      Copy code
      Switch to dark mode
      123456789
      r_result = NEW #( i_carrier_id = i_carrier_id ). r_result->name = details-name. r_result->currency_code = details-currency_code. ENDMETHOD.
  4. Before the instantiation, but after the existence and authority checks, implement a single record read from attribute instances. Read the instance in which the value of attribute carrier_id equals the value of import parameter i_carrier_id. Store the result in returning parameter r_result.

    Hint

    Remember that in internal tables with non-structured row type you address the row using pseudo-component table_line.
    1. Adjust the code as follows:

      Code Snippet
      Copy code
      Switch to dark mode
      123456789101112
      IF sy-subrc <> 0. RAISE EXCEPTION TYPE cx_abap_auth_check_exception. ENDIF. r_result = NEW #( i_carrier_id = i_carrier_id ). r_result->name = details-name. r_result->currency_code = details-currency_code.
  5. Surround the single record read access, the instantiation and the APPEND statement with a TRY... ENDTRY control structure.

    1. Adjust the code as follows:

      Code Snippet
      Copy code
      Switch to dark mode
      1234567891011
      r_result = instances[ table_line->carrier_id = i_carrier_id ]. r_result = NEW #( i_carrier_id = i_carrier_id ). r_result->name = details-name. r_result->currency_code = details-currency_code. APPEND r_result TO instances.
  6. Make sure you only create an instance and add it to attribute instances, if system exception CX_SY_ITAB_LINE_NOT_FOUND is raised.

    1. Adjust the code as follows:

      Code Snippet
      Copy code
      Switch to dark mode
      1234567891011121314
      TRY. r_result = instances[ table_line->carrier_id = i_carrier_id ]. r_result = NEW #( i_carrier_id = i_carrier_id ). r_result->name = details-name. r_result->currency_code = details-currency_code. APPEND r_result TO instances. ENDTRY.
  7. In your local class, adjust the implementation of method IF_OO_ADT_CLASSRUN~MAIN. Duplicate the call of factory method GET_INSTANCE with the same input but a different reference variable for the result (suggested name: carrier2).

    1. Adjust the code as follows:

      Code Snippet
      Copy code
      Switch to dark mode
      12345
      DATA(carrier) = lcl_carrier=>get_instance( i_carrier_id = c_carrier_id ). DATA(carrier2) = lcl_carrier=>get_instance( i_carrier_id = c_carrier_id ).
  8. Activate and test your global class as console app. Debug the code to ensure that the two calls of the factory method return the same instance of LCL_CARRIER.

    1. Press Ctrl + F3.

    2. Press F9.

Log in to track your progress & complete quizzes