You notice that your code, in its current form, allows multiple instances of LCL_CARRIER to exist, representing the same airline. To fix this, you will define and implement a factory method that ensures only one instance can be created for a given carrier ID value.
Task 1: Copy Template (Optional)
Copy the template class /LRN/CL_S4D401_OOS_INTF_USAGE. If you finished the previous exercise, you can skip this task and continue editing your class ZCL_##_SOLUTION.
Steps
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).
In the Project Explorer, right-click class /LRN/CL_S4D401_OOS_INTF_USAGE to open the context menu.
From the context menu, choose Duplicate....
Enter the name of your package in the Package field. In the Name field, enter ZCL_##_SOLUTION, where ## stands for your group number.
Choose Next.
Confirm the transport request and choose Finish.
Activate the copy.
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
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. Therefore, do not explicitly select the carrier_id attribute as an attribute to be initialized.
Navigate to the definition of LCL_CARRIER.
In the CLASS … DEFINITION statement, place the cursor on lcl_carrier and press Ctrl + 1 to invoke the available quick fixes.
From the list of available quick fixes, choose Generate factory method create.
In the Name field, enter GET_INSTANCE.
In the Select attributes to initialize section, choose Deselect All, then choose Finish.
The quick fix should have added the following method definition:
12345
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:
123
METHOD get_instance.
r_result = NEW #( i_carrier_id = i_carrier_id ).
ENDMETHOD.
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.
Navigate to the implementation of the CONSTRUCTOR method of local class LCL_CARRIER.
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.
Press Ctrl + < to add a comment sign in front of each selected code row.
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.
The code should now look like this:
12345678910111213141516171819202122
METHOD get_instance.
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 ).
IF sy-subrc <> 0.
RAISE EXCEPTION TYPE cx_abap_invalid_value.
ENDIF.
AUTHORITY-CHECK
OBJECT '/LRN/CARR'
ID '/LRN/CARR' FIELD i_carrier_id
ID 'ACTVT' FIELD '03'.
IF sy-subrc <> 0.
RAISE EXCEPTION TYPE cx_abap_auth_check_exception.
ENDIF.
r_result = NEW #( i_carrier_id = i_carrier_id ).
ENDMETHOD.
Since you have removed the RAISE EXCEPTION statements from the implementation of the CONSTRUCTOR method, now delete the two corresponding RAISING additions in the signature of the CONSTRUCTOR method definition.
Navigate to the definition of the CONSTRUCTOR method in LCL_CARRIER.
Adjust the method definition as follows:
123456
METHODS constructor
IMPORTING
i_carrier_id TYPE /dmo/carrier_id
.
Return to the implementation of the GET_INSTANCE method. 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.
Adjust the code as follows:
1234567
SELECT SINGLE
FROM /lrn/carrier
FIELDS concat_with_space( carrier_id, name, 1 ) AS name,
currency_code
WHERE carrier_id = @i_carrier_id
INTO @DATA(details).
After the instantiation of the carrier object, assign the values from the details structure to the corresponding instance attributes of the new instance.
Adjust the code as follows:
12345
r_result = NEW #( i_carrier_id = i_carrier_id ).
r_result->name = details-name.
r_result->currency_code = details-currency_code.
ENDMETHOD.
Use a quick fix to add raising declarations for the two exceptions.
Choose the warning icon with the light bulb on the left-hand side of the first RAISE EXCEPTION statement to invoke the available quick fixes.
From the list of available quick fixes, choose Add raising declaration.
Repeat this for the other RAISE EXCEPTION statement.
The quick fix should have adjusted the code as follows:
12345678
CLASS-METHODS get_instance
IMPORTING
i_carrier_id TYPE /dmo/carrier_id
RETURNING
value(r_result) TYPE REF TO lcl_carrier
RAISING
cx_abap_invalid_value
cx_abap_auth_check_exception.
Restrict the instantiation of local class LCL_CARRIER. Make sure instantiation is only allowed within the class itself.
Navigate to the definition of LCL_CARRIER.
Adjust the code as follows:
1
CLASS lcl_carrier DEFINITION CREATE PRIVATE.
Move the definition of the CONSTRUCTOR method to the private section.
Note
This is optional, but should be done to improve readability.
Copy and paste the METHODS statement for method CONSTRUCTOR from the public section to the private section.
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.
Adjust the code as follows:
12
DATA(carrier) = lcl_carrier=>get_instance( i_carrier_id = c_carrier_id ).
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.
Adjust the code as follows:
12
the_carrier = lcl_carrier=>get_instance( i_carrier_id = some_flight_data-carrier_id ).
Activate and test your global class as console app. Debug the execution of the GET_INSTANCE method.
Press Ctrl + F3.
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). Then extend the implementation of the factory method: After the creation of an instance, store the reference to the new instance in attribute instances. Only create a new instance if instances does not yet reference an object for the requested carrier ID.
Steps
In local class LCL_CARRIER, use a quick fix to create a table type for this class (suggested name: tt_carriers).
Navigate to the definition of local class LC_CARRIER.
In the CLASS … DEFINITION statement, place the cursor on lcl_carrier and press Ctrl + 1 to invoke the available quick fixes.
From the list of available quick fixes, choose Generate table type for lcl_carrier.
In the new TYPES statement, replace tab with tt_carriers.
The TYPES statement should now look like this:
12
TYPES: tt_carriers TYPE STANDARD TABLE OF REF TO lcl_carrier
WITH DEFAULT KEY.
Define a private static attribute based on the just created table type (suggested name: instances).
Scroll down to the PRIVATE SECTION statement.
Adjust the code as follows:
12
PRIVATE SECTION.
CLASS-DATA instances TYPE tt_carriers.
Adjust the implementation of the factory method GET_INSTANCE. After the instantiation, add a reference to the new instance to the instances attribute.
Adjust the code as follows:
123456
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.
ENDMETHOD.
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 data using pseudo component table_line.
Adjust the code as follows:
1234567891011
IF sy-subrc <> 0.
RAISE EXCEPTION TYPE cx_abap_auth_check_exception.
ENDIF.
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.
Surround the single record read access, the instantiation, the value assignments to the attributes, and the APPEND statement with a TRY... ENDTRY control structure.
Adjust the code as follows:
123456789
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.
Make sure you only create an instance and add it to attribute instances, if system exception CX_SY_ITAB_LINE_NOT_FOUND is raised.
Adjust the code as follows:
123456789
TRY.
r_result = instances[ table_line->carrier_id = i_carrier_id ].
CATCH cx_sy_itab_line_not_found.
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.
In your global class, adjust the implementation of method IF_OO_ADT_CLASSRUN~MAIN: Implement a second call to the GET_INSTANCE factory method with the same input as the existing call, but a different reference variable for the result (suggested name: carrier2).
Adjust the code as follows:
12
DATA(carrier) = lcl_carrier=>get_instance( i_carrier_id = c_carrier_id ).
DATA(carrier2) = lcl_carrier=>get_instance( i_carrier_id = c_carrier_id ).
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.
Press Ctrl + F3.
Press F9.