使用继承

学习目标

  • 使用继承。

向上和向下转换

要实例化子类,请使用相关类的类型声明参考变量。然后使用 NEW 运算符创建 实例。您必须将值传递到构造函数的所有强制参数。

使用超类参考

具有超类类型(在本例中为 lcl_plane)的参考变量也可以保存对具有子类类型的对象的参考。这是有意义的,因为客机或货机是飞机。

但是,具有超类类型的参考变量仅了解在超类中定义的组件。因此,当您使用此类型的参考来管理子类的实例时,即使参考指向的实际对象是子类的实例,也只能处理超级类中的组件。

将对象参考分配到不同类型的参考变量称为转换。将指向子类的参考分配到具有超类类型的参考变量称为向上转换,因为您在继承层次结构中向一个类型的分配更高一些。

Up-Cast 后的调用方法

使用类型为 ref to lcl_plane 的参考变量管理客机实例时,不能使用它来访问属于客机类的组件,因为飞机类不知道它们是否存在,语法检查无法知道参考在运行时实际指向的对象类型。

但是,使用参考,您可以访问原始定义包含在类中的方法。图中的示例中的 get_attributes 方法就是这种情况。此处的问题是将调用哪种方法实施。在这种情况下,系统会在运行时查看参考变量指向的对象类型,并从相应的类调用实施。换句话说,尽管我们使用的是飞机参考来管理客机类的实例,但它仍然是来自客机类的方法实现。

将通用参考分配到特定类型

您始终可以将具有子类类型的参考变量分配到具有超类类型 的参考变量。例如,您始终可以将客机参考分配到飞机参考。正如我们所看到的,这是因为客机是飞机。但是,在相反方向分配参考变量更为棘手。这是因为平面参考可以包含对客机类以外的其他类的参考。也就是说,每架客机都是飞机,不是每架飞机都是客机。

给定对象参考类型为 ref 到 lcl_plane 的平面和类型为 ref to lcl_passenger_plane 的 passenger_plane,系统将始终允许您写入平面 = passenger_plane。但是,分配 passenger_plane = plane 会导致语法错误,因为语法检查无法充分确定此时平面参考将实际包含对客机对象的引用。

CAST 运算符

语法检查不允许将超类参考直接分配给子类参考。但是,您仍可以使用 CAST 运算符执行此操作。铸造意味着查看一个对象,就好像它有不同的类型。在示例中,我们告诉系统将"飞机"视为客机类的实例。这在语法上是正确的,因为我们现在假装表达式右侧的"平面"与左侧的"乘客"类型相同。但是,仍然无法保证飞机实际包含客机参考,如果实际上在运行时不包含乘客飞机参考,则无法进行分配。未受到保护,此分配可能导致运行时错误。

保护 Down-Cast

如果不确定参考变量指向的对象实际上是正确子类的实例,则可以使用逻辑表达式 IS INSTANCE OF 进行检查。在此示例中,仅当飞机实际指向 lcl_passenger_plane 类的实例时,才会处理对客机参考的分配。

试用:Up-Cast 和 Down-Cast

  1. 创建实施接口 IF_OO_ADT_CLASSRUN 的新类。

  2. 切换到 局部类型 标签并将以下代码片段复制到编辑器中:

CLASS lcl_plane DEFINITION.
PUBLIC SECTION.
TYPES: BEGIN OF ts_attributes,
name TYPE string,
value TYPE string,
END OF ts_attributes,
* declare table - do not allow the same attribute to be used more than once
tt_attributes TYPE SORTED TABLE OF ts_attributes WITH UNIQUE KEY name.
METHODS constructor IMPORTING iv_manufacturer TYPE string
iv_type TYPE string.
METHODS: get_Attributes RETURNING VALUE(rt_Attributes) TYPE tt_attributes.
PROTECTED SECTION.
DATA manufacturer TYPE string.
DATA type TYPE string.
PRIVATE SECTION.
ENDCLASS.
CLASS lcl_plane IMPLEMENTATION.
METHOD constructor.
manufacturer = iv_manufacturer.
type = iv_type.
ENDMETHOD.
METHOD get_attributes.
rt_attributes = VALUE #( ( name = 'MANUFACTURER' value = manufacturer )
( name = 'TYPE' value = type ) ) .
ENDMETHOD.
ENDCLASS.
CLASS lcl_cargo_plane DEFINITION INHERITING FROM lcl_plane.
PUBLIC SECTION.
METHODS constructor IMPORTING iv_manufacturer TYPE string
iv_type TYPE string
iv_cargo TYPE i.
METHODS get_attributes REDEFINITION.
private section.
data cargo type i.
ENDCLASS.
CLASS lcl_cargo_plane IMPLEMENTATION.
METHOD constructor.
super->constructor( iv_manufacturer = iv_manufacturer iv_type = iv_type ).
cargo = iv_cargo.
ENDMETHOD.
METHOD get_attributes.
* method uses protected attributes of superclass
rt_attributes = value #( ( name = 'MANUFACTURER' value = manufacturer )
( name = 'TYPE' value = type )
( name ='CARGO' value = cargo ) ).
ENDMETHOD.
ENDCLASS.
CLASS lcl_passenger_plane DEFINITION INHERITING FROM lcl_Plane.
PUBLIC SECTION.
METHODS constructor IMPORTING iv_manufacturer TYPE string
iv_type TYPE string
iv_seats TYPE i.
METHODS get_Attributes REDEFINITION.
private section.
data seats type i.
ENDCLASS.
CLASS lcl_passenger_plane IMPLEMENTATION.
METHOD constructor.
super->constructor( iv_manufacturer = iv_manufacturer iv_type = iv_type ).
ENDMETHOD.
METHOD get_attributes.
* Redefinition uses call of superclass implementation
rt_attributes = super->get_attributes( ).
rt_Attributes = value #( base rt_attributes ( name = 'SEATS' value = seats ) ).
ENDMETHOD.
ENDCLASS.
  1. 切换到 全局类 标签并将以下代码片段粘贴到方法 if_oo_adt_classrun~main() 的实施中:

DATA passenger TYPE REF TO lcl_passenger_Plane.
DATA cargo TYPE REF TO lcl_cargo_plane.
DATA plane TYPE REF TO lcl_Plane.
passenger = NEW #( iv_manufacturer = 'BOEING' iv_type = '737-800' iv_seats = 130 ) .
cargo = NEW #( iv_manufacturer = 'AIRBUS' iv_type = 'A340' iv_cargo = 60000 ).
out->write( 'Output using passenger plane object reference' ).
out->write( passenger->Get_attributes( ) ).
plane = passenger.
out->write( 'Output using superclass object reference' ).
out->write( plane->get_attributes( ) ).
* Can't use the superclass reference to get the number of seats of the
* passenger plane, because the relevent method isn't declared in the superclass.
* plane->get_seats( ).
* Can't assign the plane directly to a passenger plane reference variable, because
* not every plane is a passenger plane.
*passenger = plane.
* Make sure the plane is actually a passenger plane, then force the cast.
IF plane IS INSTANCE OF lcl_passenger_plane.
passenger = CAST #( plane ).
ENDIF.
ENDMETHOD.
  1. 选择 CTRL + F3 激活类,然后选择 F9 将其作为控制台应用执行。

  2. 浏览源代码以熟悉概念。

使用超类参考

在上一练习中,您为类 LCL_PASSENGER_FLIGHT 和 LCL_CARGO_FLIGHT 创建了通用超类 LCL_FLIGHT。现在,您将修改本地类 LCL_CARRIER 以使用继承关系。您将键入用作超类类型的参数的参考变量,并且定义单个实例属性**航班**,而不是两个单独的实例属性 passenger_flightcargo_flights,在其中可以存储对所有航班实例的引用,无论它们是乘客航班还是货物航班。

模板:

  • /LRN/CL_S4D401_OOS_INHERITANCE(全局类)

解决方案:

  • /LRN/CL_S4D401_OOS_INH_USAGE(全局类)

任务 1: 复制模板(可选)

复制模板类 /LRN/CL_S4D401_OOS_INHERITANCE。如果已完成上一练习,则可以跳过此任务并继续编辑类 ZCL_##_SOLUTION

步骤

  1. 将类 /LRN/CL_S4D401_OOS_INHERITANCE 复制到您自己的包中的类(建议名称:ZCL_##_SOLUTION,其中 ## 代表组编号)。

  2. 在项目浏览器中,右键单击类 /LRN/CL_S4D401_OOS_INHERITANCE 以打开上下文菜单。

  3. 从上下文菜单中,选择复制....

  4. 在包字段中输入的名称。在名称字段中,输入 ZCL_##_SOLUTION,其中 ## 代表组编号。

  5. 选择下一步

  6. 确认传输请求并选择完成

  7. 激活复制。

  8. Ctrl + F3 激活类。

任务 2: 通用参数

在局部类 LCL_CARRIER 中,调整方法 FIND_PASSENGER_FLIGHTFIND_CARGO_FLIGHT 的定义。在这两种方法中,键入导出参数 e_flight 作为基于本地类 LCL_FLIGHT 的参考变量。

步骤

  1. 在局部类 LCL_CARRIER 中,调整方法 FIND_CARGO_FLIGHT 的定义。键入返回参数 e_flightLCL_FLIGHT 而不是 LCL_CARGO_FLIGHT。确保保存更改。

  2. 如下调整代码:

METHODS find_cargo_flight
IMPORTING
i_airport_from_id TYPE /dmo/airport_from_id
i_airport_to_id   TYPE /dmo/airport_to_id
i_from_date       TYPE /dmo/flight_date
i_cargo           TYPE /lrn/plane_actual_load
EXPORTING
e_flight          TYPE REF TO lcl_flight
e_days_later      TYPE i.
  1. 在方法 FIND_CARGO_FLIGHT 的实施中分析局部变量**航班**的使用。

  2. 要显示参考变量**航班**的类型,请将光标置于变量名称上,然后按 F2

  3. 在全局类的方法 IF_OO_ADT_CLASSRUN~MAIN 中分析方法 FIND_CARGO_FLIGHT 的调用。

  4. 要显示参考变量 cargo_flight 的类型,请将光标置于变量名称上,然后按 F2

  5. 以相同方式调整方法 FIND_PASSENGER_FLIGHT 的定义。键入返回参数 e_flightLCL_FLIGHT 而不是 LCL_PASSENGER_FLIGHT

  6. 如下调整代码:

METHODS find_passenger_flight
IMPORTING
i_airport_from_id TYPE /dmo/airport_from_id
i_airport_to_id   TYPE /dmo/airport_to_id
i_from_date       TYPE /dmo/flight_date
i_seats           TYPE i
EXPORTING
e_flight          TYPE REF TO lcl_flight
e_days_later      TYPE i.
  1. 激活代码以验证此更改是否也不会导致任何语法错误。

  2. Ctrl + F3 激活代码。

任务 3: 通用属性

在本地类 LCL_CARRIER 中,定义一个私有实例属性**航班**,您可以在其中同时存储对 LCL_PASSENGER_FLIGHTLCL_CARGO_FLIGHT 实例的参考。在 CONSTRUCTOR 方法的实施中,使用属性 passenger_flightscargo_flights 的组合内容填充该属性。

步骤

  1. 在局部类 LCL_FLIGHT 中,使用快速修复生成基于此类的公共表类型(表类型的建议名称:标签)。

  2. 转到本地类 LCL_FLIGHT 的定义。

  3. 在 CLASS 语句中,将光标置于 lcl_flight 上并按 Ctrl + 1 调用可用的快速修复。

  4. 从可用快速修复列表中,选择为 lcl_flight 生成表类型

  5. 新的 TYPES 语句应如下所示:

TYPES tab TYPE STANDARD TABLE OF REF TO lcl_flight WITH DEFAULT KEY.
  1. 在本地类 LCL_CARRIER 的专用部分,定义新的实例属性**航班**。使用刚刚在本地类 LCL_FLIGHT 中创建的表类型键入它。

  2. 如下调整代码:

DATA passenger_flights TYPE lcl_passenger_flight=>tt_flights.
DATA cargo_flights     TYPE lcl_cargo_flight=>tt_flights.
DATA flights           TYPE lcl_flight=>tab.
  1. 调整局部类 LCL_CARRIERCONSTRUCTOR 方法的实施。调用 LCL_PASSENGER_FLIGHT 的方法 GET_FLIGHTS_BY_CARRIER 后,将 passenger_flight 中存储的所有对象参考添加到**航班**。

  2. 转到本地类 LCL_CARRIERCONSTRUCTOR 方法的实施。

  3. 在 ENDMETHOD 语句之前,添加以下代码:

LOOP AT passenger_flights INTO DATA(passflight).
APPEND passflight TO flights.
ENDLOOP.

如果您更喜欢表理解,请改为添加以下代码:

flights = VALUE #( BASE flights
FOR pflight IN passenger_flights (
pflight
)
).
  1. 同样,对 cargo_flights 实施循环,并将这些引用添加到**航班**中。

  2. 在 ENDMETHOD 语句之前,添加以下代码:

LOOP AT cargo_flights INTO DATA(cargoflight).
APPEND cargoflight TO flights.
ENDLOOP.

如果您更喜欢表理解,请改为添加以下代码:

flights = VALUE #( BASE flights
FOR cflight IN cargo_flights (
cflight
)
).

任务 4: 通用访问和向下转换

消除本地类 LCL_CARRIER 中的实例属性 passenger_flight 和 cargo_flight,并将其替换为新属性航班。如果代码访问子类的特定组件,则实施向下转换。

步骤

  1. 在本地类 LCL_CARRIER 的定义部分中,移除或注释 passenger_flightscargo_flights 属性的定义。

  2. 选择两个 DATA 语句并按 Ctrl + <,在每个代码行前添加注释符号:

*  DATA passenger_flights TYPE lcl_passenger_flight=>tt_flights.
*  DATA cargo_flights     TYPE lcl_cargo_flight=>tt_flights.
  1. 在方法 CONSTRUCTOR 中,使用内联声明将 passenger_flightscargo_flights 声明为局部变量。

  2. 如下调整代码:

DATA(passenger_flights) =
lcl_passenger_flight=>get_flights_by_carrier(
i_carrier_id = i_carrier_id ).
DATA(cargo_flights) =
lcl_cargo_flight=>get_flights_by_carrier(
i_carrier_id = i_carrier_id ).
  1. 方法使用内联声明调用 后,使用局部变量 passenger_flights 中的行数填充专用实例属性(建议名称:pf_count)。

提示

首先实施值分配。然后使用快速修复创建属性声明。

  1. 如下调整代码:

DATA(passenger_flights) =
lcl_passenger_flight=>get_flights_by_carrier(
i_carrier_id = i_carrier_id ).
pf_count = lines( passenger_flights ).
  1. 将光标置于 pf_count 上,按 Ctrl + 1 并选择声明属性 pf_count

  2. 同样,用局部变量 cargo_flights 中的行数填充私有实例属性(建议名称:cf_count)。

  3. 如下调整代码:

DATA(cargo_flights) =
lcl_cargo_flight=>get_flights_by_carrier(
i_carrier_id = i_carrier_id ).
cf_count = lines( cargo_flights ).
  1. 将光标置于 cf_count 上,按 Ctrl + 1 并选择声明属性 cf_count

  2. 在方法 GET_OUTPUT 中,将嵌入式 LINES 函数替换为刚刚定义的属性。

  3. 如下调整代码:

METHOD get_output.
APPEND |{ 'Carrier Name:'(001)       } { me->name }| TO r_result.
APPEND |{ 'Passenger Flights:'(002)  } { pf_count }| TO r_result.
APPEND |{ 'Average free seats:'(003) } { get_average_free_seats(  ) }| TO r_result.
APPEND |{ 'Cargo Flights:'(004)      } { cf_count }| TO r_result.
ENDMETHOD.
  1. 在方法 FIND_CARGO_FLIGHT 中,将属性 cargo_flights 替换为属性**航班**。

  2. 如下调整代码:

*  LOOP AT me->cargo_flights INTO DATA(flight)
LOOP AT me->flights INTO DATA(flight)
  1. 在方法 GET_FREE_CAPACITY 的调用中添加 CAST 表达式。

  2. 如下调整代码:

IF connection_details-airport_from_id = i_airport_from_id
AND connection_details-airport_to_id = i_airport_to_id
*    AND flight->get_free_capacity(  ) >= i_cargo.
AND CAST lcl_cargo_flight( flight )->get_free_capacity(  ) >= i_cargo.
  1. 要确保仅考虑 LCL_CARGO_FLIGHT 的实例,请扩展 LOOP 的 WHERE 条件。

提示

请记住,在具有基本行类型的内部表中,使用伪组件 table_line 寻址内容。

  1. 如下调整代码:

LOOP AT me->flights INTO DATA(flight)
WHERE table_line->flight_date >= i_from_date
AND table_line IS INSTANCE OF lcl_cargo_flight.
  1. 以相同方式调整方法 FIND_PASSENGER_FLIGHT 的实施。

  2. 如下调整代码:

LOOP AT me->flights INTO DATA(flight)
WHERE table_line->flight_date >= i_from_date
AND table_line IS INSTANCE OF lcl_passenger_flight.
DATA(connection_details) = flight->get_connection_details(  ).
IF connection_details-airport_from_id = i_airport_from_id
AND connection_details-airport_to_id = i_airport_to_id
AND CAST lcl_passenger_flight( flight )->get_free_seats( ) >= i_seats.
DATA(days_later) = flight->flight_date - i_from_date.
IF days_later < e_days_later. "earlier than previous one?
e_flight = flight.
e_days_later = days_later.
ENDIF.
ENDIF.
ENDLOOP.
  1. 调整 GET_AVERAGE_FREE_SEATS 方法的实施。在缩减表中使用内部表**航班**,并向缩减中添加 CAST 表达式和 WHERE 条件。所需数量的乘客航班存储在上面创建的属性 pf_count 中。

  2. 如下调整代码:

METHOD get_average_free_seats.
* Table Reduction
********************************************************
*    r_result = REDUCE #(
*                 INIT i = 0
*                 FOR flight IN passenger_flights
*                 NEXT i += flight->get_free_seats( )
*                       ) / lines( passenger_flights ).
r_result = REDUCE #(
INIT i = 0
FOR flight IN flights
WHERE ( table_line IS INSTANCE OF lcl_passenger_flight )
NEXT i += CAST lcl_passenger_flight( flight )->get_free_seats( )
) / pf_count.
ENDMETHOD.
  1. 以控制台应用形式激活并测试全局类。

  2. Ctrl + F3

  3. F9