实施工厂方法
学习目标
使用工厂方法。
工厂方法
有时,类需要控制其实例。这可能是为了防止创建多个实例。为此,必须阻止其用户自己创建实例。通过在 CLASS DEFINITION 语句中使用 CREATE PRIVATE 附加在 ABAP 中执行此操作。现在,任何在 类外使用 NEW 运算符的尝试都将导致语法错误。请注意,其他编程语言通过更改构造函数方法的可见性来实现此效果。ABAP 不支持此操作。

由于 类的用户无法创建自己的实例,因此 类必须提供公共静态方法,用于创建实例并将其返回给用户。这称为工厂方法。
试用:工厂方法实施
创建实施接口 IF_OO_ADT_CLASSRUN 的新类。
切换到 局部类型 标签并将以下代码片段复制到编辑器中:
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.
切换到 全局类 标签并将以下代码片段粘贴到方法 if_oo_adt_classrun~main() 的实施中:
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.
使用 Ctrl + F3 激活类。
通过编码进行导航。特别是,调试 main() 方法并记录分配给工厂方法返回对象的内部实例编号。
单例模式
单例模式旨在确保应用程序中只有一个类实例。这可以是类的单个实例,在我们的案例中,可以是每个关键属性组合的单个实例。
观看以下视频,了解有关单例模式的详细信息。
视频:本节含 SAP 官方视频(视频 ID
1_6tqf4vhw),需在 learning.sap.com 在线观看。
视频:本节含 SAP 官方视频(视频 ID
1_dmvj9k29),需在 learning.sap.com 在线观看。
视频:本节含 SAP 官方视频(视频 ID
1_w69iw7td),需在 learning.sap.com 在线观看。
试用:单例模式实施
创建实施接口 IF_OO_ADT_CLASSRUN 的新类。
切换到 局部类型 标签并将以下代码片段复制到编辑器中:
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.
切换到 全局类 标签并将以下代码片段粘贴到方法 if_oo_adt_classrun~main() 的实施中:
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.
使用 Ctrl + F3 激活类。
通过编码进行导航。特别是,调试 main() 方法并记录分配给工厂方法返回对象的内部实例编号。
实施工厂方法
您注意到当前形式的代码允许存在代表同一航空公司的多个 LCL_CARRIER 实例。要修复此问题,您将定义并实施工厂方法,以确保只能为给定承运方标识值创建一个实例。
模板:
/LRN/CL_S4D401_OOS_INTF_USAGE(全局类)
解决方案:
/LRN/CL_S4D401_OOS_FACTORY(全局类)
任务 1: 复制模板(可选)
复制模板类 /LRN/CL_S4D401_OOS_INTF_USAGE。如果已完成上一练习,则可以跳过此任务并继续编辑类 ZCL_##_SOLUTION。
步骤
将类
/LRN/CL_S4D401_OOS_INTF_USAGE复制到您自己的包中的类(建议名称:ZCL_##_SOLUTION,其中 ## 代表组编号)。在项目浏览器中,右键单击类
/LRN/CL_S4D401_OOS_INTF_USAGE以打开上下文菜单。从上下文菜单中,选择复制....
在包字段中输入包的名称。在名称字段中,输入
ZCL_##_SOLUTION,其中 ## 代表组编号。选择下一步。
确认传输请求并选择完成。
激活复制。
按
Ctrl + F3激活类。
任务 2: 定义工厂方法
使用快速修复为本地类 LCL_CARRIER 定义工厂方法(建议名称:GET_INSTANCE)。在工厂方法中,在实际创建实例之前执行存在性检查和权限检查。然后限制此类的实例创建以强制使用工厂方法。
步骤
在局部类
LCL_CARRIER中,使用快速修复定义工厂方法。
提示
缺省情况下,将构造函数参数添加到工厂方法的签名中。因此,不要显式选择 carrier_id 属性作为要初始化的属性。
导航至
LCL_CARRIER的定义。在 CLASS…DEFINITION 语句中,将光标放在
lcl_carrier上并按Ctrl + 1调用可用的快速修复。从可用快速修复列表中,选择生成工厂方法创建。
在名称字段中,输入
GET_INSTANCE。在选择要初始化的属性部分,选择取消全选,然后选择完成。
快速修复应已添加以下方法定义:
CLASS-METHODS get_instance
IMPORTING
i_carrier_id TYPE /dmo/carrier_id
RETURNING
value(r_result) TYPE REF TO lcl_carrier.
…以及以下实施:
METHOD get_instance.
r_result = NEW #( i_carrier_id = i_carrier_id ).
ENDMETHOD.
将 SELECT 语句和 AUTHORITY-CHECK 语句从
CONSTRUCTOR移动到工厂方法的实施。确保在实例化之前执行检查。导航到本地类
LCL_CARRIER的CONSTRUCTOR方法的实施。选择 SELECT 语句和 AUTHORITY-CHECK 语句,包括系统字段
SY-SUBRC的评估以及引发异常,然后按Ctrl + C将代码片段复制到剪贴板。按
Ctrl + <在每个所选代码行前添加注释符号。导航到方法
GET_INSTANCE的实施,将光标置于参数r_result的值分配之前,然后按Ctrl + V插入代码片段。代码现在应如下所示:
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.
由于已从
CONSTRUCTOR方法的实施中删除 RAISE EXCEPTION 语句,因此现在删除CONSTRUCTOR方法定义签名中的两个相应 RAISING 附加项。导航至
LCL_CARRIER中CONSTRUCTOR方法的定义。按如下所示调整方法定义:
METHODS constructor
IMPORTING
i_carrier_id TYPE /dmo/carrier_id
* RAISING cx_abap_invalid_value
* cx_abap_auth_check_exception
.
返回到 GET_INSTANCE 方法的实施。在 SELECT 语句中,调整 INTO 子句。将结果存储在内联声明结构中(建议名称:
详细信息),而不是实例属性**名称**和currency_code。如下调整代码:
SELECT SINGLE
FROM /lrn/carrier
FIELDS concat_with_space( carrier_id, name, 1 ) AS name,
currency_code
WHERE carrier_id = @i_carrier_id
* INTO ( @me->name, @me->currency_code ).
INTO @DATA(details).
实例化运输公司对象后,将**
详细信息**结构中的值分配到新实例的相应实例属性。如下调整代码:
r_result = NEW #( i_carrier_id = i_carrier_id ).
r_result->name = details-name.
r_result->currency_code = details-currency_code.
ENDMETHOD.
使用快速修复为这两个异常添加引发声明。
选择第一个 RAISE EXCEPTION 语句左侧带有灯泡的警告图标以调用可用的快速修复。
从可用快速修复列表中,选择添加引发声明。
对其他 RAISE EXCEPTION 语句重复此操作。
快速修复应按如下方式调整代码:
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.
限制本地类
LCL_CARRIER的实例化。确保仅在类本身内允许实例化。导航至
LCL_CARRIER的定义。如下调整代码:
CLASS lcl_carrier DEFINITION CREATE PRIVATE.
将
CONSTRUCTOR方法的定义移动到专用部分。
注意
这是可选的,但为了提高可读性,应该这样做。
将方法
CONSTRUCTOR的 METHODS 语句从公共部分复制并粘贴到专用部分。在全局类的
IF_OO_ADT_CLASSRUN~MAIN方法中,将 NEW 表达式替换为工厂方法GET_INSTANCE的调用。如下调整代码:
* DATA(carrier) = NEW lcl_carrier( i_carrier_id = c_carrier_id ).
DATA(carrier) = lcl_carrier=>get_instance( i_carrier_id = c_carrier_id ).
在本地测试类
LTCL_FIND_FLIGHTS的CLASS_SETUP方法中,将 NEW 表达式替换为工厂方法GET_INSTANCE的调用。如下调整代码:
* the_carrier = NEW lcl_carrier( i_carrier_id = some_flight_data-carrier_id ).
the_carrier = lcl_carrier=>get_instance( i_carrier_id = some_flight_data-carrier_id ).
以控制台应用形式激活并测试全局类。调试
GET_INSTANCE方法的执行。按
Ctrl + F3。按
F9。
任务 3: 扩展工厂方法
在局部类 LCL_CARRIER 中,定义一个静态属性,可在其中存储对 LCL_CARRIER 实例的参考(建议名称:实例)。然后扩展工厂方法的实施:创建实例后,将新实例的参考存储在属性**实例中。仅当实例尚未参考请求承运方标识的对象时,才创建新实例**。
步骤
在局部类
LCL_CARRIER中,使用快速修复为此类创建表类型(建议名称:tt_carriers)。导航到本地类
LC_CARRIER的定义。在 CLASS…DEFINITION 语句中,将光标放在
lcl_carrier上并按Ctrl + 1调用可用的快速修复。从可用快速修复列表中,选择为 lcl_carrier 生成表类型。
在新的 TYPES 语句中,将
tab替换为tt_carriers。TYPES 语句现在应如下所示:
TYPES: tt_carriers TYPE STANDARD TABLE OF REF TO lcl_carrier
WITH DEFAULT KEY.
根据刚创建的表类型(建议名称:
实例)定义专用静态属性。向下滚动至 PRIVATE SECTION 语句。
如下调整代码:
PRIVATE SECTION.
CLASS-DATA instances TYPE tt_carriers.
调整工厂方法
GET_INSTANCE的实施。实例化后,将新实例的引用添加到 inst**anc**es 属性。如下调整代码:
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.
在实例化之前,但在存在性和权限检查后,实施从属性**
实例**中读取的单个记录。读取属性carrier_id的值等于导入参数i_carrier_id值的实例。将结果存储在返回参数r_result中。
提示
请记住,在具有非结构化行类型的内部表中,使用伪组件 table_line 寻址数据。
如下调整代码:
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.
将单个记录读取访问、实例化、属性值分配以及 APPEND 语句与 TRY...ENDTRY 控制结构。
如下调整代码:
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.
如果引发系统异常 CX_SY_ITAB_LINE_NOT_FOUND,请确保仅创建实例并将其添加到属性**
实例**。如下调整代码:
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.
在全局类中,调整方法
IF_OO_ADT_CLASSRUN~MAIN的实施:使用与现有调用相同的输入,但结果有不同的参考变量(建议名称:carrier2)对GET_INSTANCE工厂方法进行第二次调用。如下调整代码:
DATA(carrier) = lcl_carrier=>get_instance( i_carrier_id = c_carrier_id ).
DATA(carrier2) = lcl_carrier=>get_instance( i_carrier_id = c_carrier_id ).
以控制台应用形式激活并测试全局类。调试代码以确保工厂方法的两次调用返回相同的
LCL_CARRIER实例。按
Ctrl + F3。按
F9。