继承与类型转换 练习

继承与类型转换 练习

#practice #inheritance #casting #polymorphism

📌核心模式
关键词 答案
继承 INHERITING FROM,子类获得父类的属性和方法
重定义 REDEFINITION,子类重新实现父类的公有/受保护方法
向上转换 ** widening cast,子类引用赋给父类引用,始终安全**
向下转换 narrowing cast,父类引用赋给子类引用,需要运行时检查
多态 通过重定义实现,同一方法调用在不同子类中有不同行为

题目1 - [记忆] 继承的基本概念

请说明ABAP中继承(Inheritance)的基本概念,并写出继承的语法。

📌查看答案
继承是指一个类(子类/派生类)可以继承另一个类(父类/超类)的属性和方法,并可以添加新的成员或重定义已有的方法。

语法:

1
2
3
4
CLASS lcl_child DEFINITION INHERITING FROM lcl_parent.
PUBLIC SECTION.
" 新增或重定义的方法
ENDCLASS.

继承的特点:

  1. 子类自动拥有父类中 PUBLIC 和 PROTECTED 区域的所有成员
  2. 子类无法直接访问父类的 PRIVATE 成员(但可以通过父类的公有/受保护方法间接访问)
  3. ABAP只支持单继承(一个类只能有一个直接父类)
  4. 所有类默认继承自系统根类 OBJECT
  5. 继承关系形成类层次结构(继承树)

题目2 - [记忆] 方法的重定义(REDEFINITION)

请说明方法重定义的规则和限制。

📌查看答案
方法重定义(REDEFINITION)的规则:
  1. 可重定义的范围:只能重定义父类中 PUBLIC SECTION 或 PROTECTED SECTION 的实例方法
  2. 不可重定义的
    • PRIVATE 方法(子类无法看到)
    • CONSTRUCTOR(每个类有自己的构造函数)
    • 静态方法(CLASS-METHODS)
  3. 签名一致:重定义的方法必须保持与父类方法相同的参数接口(参数名、类型、传递方式)
  4. 语法
    1
    METHODS: parent_method REDEFINITION.
  5. 调用父类版本:在重定义的方法内可以使用 super->parent_method( ) 调用父类的原始实现
  6. 目的:实现多态,让子类提供特定于自己的行为

题目3 - [记忆] PROTECTED区域的作用

请解释PROTECTED可见性区域在继承中的特殊作用。

📌查看答案
PROTECTED区域在继承中的特殊作用:
  1. 子类可访问:PROTECTED成员可以被类本身及其所有子类(包括间接子类)直接访问
  2. 外部不可访问:类层次结构之外的代码无法直接访问PROTECTED成员
  3. 典型用途
    • 供子类重定义的方法的原始实现
    • 子类可能需要直接访问的辅助属性或方法
    • 为子类提供的”钩子方法”(可重定义的模板方法)
  4. 与PRIVATE的区别:PRIVATE成员对子类完全不可见,子类只能通过PUBLIC或PROTECTED方法间接访问
  5. 与PUBLIC的区别:PUBLIC成员对所有人可见,而PROTECTED仅在类层次内部可见

设计建议:供子类使用但不想暴露给外部的成员放在PROTECTED区域。


题目4 - [记忆] 向上转换(Up-cast / Widening Cast)

请解释什么是向上转换(Up-cast),并说明为什么它是安全的。

📌查看答案
向上转换(Up-cast / Widening Cast)是将子类类型的引用变量赋值给父类类型的引用变量。

语法:

1
2
3
4
5
DATA: lo_parent TYPE REF TO lcl_parent,
lo_child TYPE REF TO lcl_child.

CREATE OBJECT lo_child.
lo_parent = lo_child. " 向上转换

为什么安全

  • 子类是父类的扩展,子类对象”是一种”(is-a)父类对象
  • 子类包含父类的所有公有接口方法,通过父类引用调用这些方法总是可行的
  • 这符合”里氏替换原则”(LSP):子类对象可以替换父类对象使用

效果

  • 通过父类引用只能调用父类中定义的方法(即使实际对象是子类实例)
  • 如果子类重定义了某个方法,通过父类引用调用时执行的是子类的重定义版本(多态)

题目5 - [记忆] 向下转换(Down-cast / Narrowing Cast)

请解释什么是向下转换(Down-cast),为什么它可能是危险的,以及ABAP中如何安全地进行向下转换。

📌查看答案
向下转换(Down-cast / Narrowing Cast)是将父类类型的引用变量赋值给子类类型的引用变量。

危险性:父类引用可能指向的不是目标子类的对象,此时赋值会导致运行时错误。

语法(使用 ?= 操作符):

1
2
3
4
DATA: lo_parent TYPE REF TO lcl_parent,
lo_child TYPE REF TO lcl_child.

lo_child ?= lo_parent. " 向下转换,可能失败

安全的向下转换方式

  1. 使用 CATCH 捕获异常:
    1
    2
    3
    4
    5
    TRY.
    lo_child ?= lo_parent.
    CATCH cx_sy_move_cast_error.
    " 处理转换失败
    ENDTRY.
  2. 使用 IS INSTANCE OF 检查(推荐):
    1
    2
    3
    IF lo_parent IS INSTANCE OF lcl_child.
    lo_child ?= lo_parent.
    ENDIF.
  3. 使用内联声明(740 SP8+):
    1
    2
    3
    IF lo_parent IS INSTANCE OF lcl_child.
    DATA(lo_child) = CAST lcl_child( lo_parent ).
    ENDIF.

题目6 - [记忆] 多态的实现机制

请说明ABAP中多态(Polymorphism)的实现机制。

📌查看答案
多态的实现机制:
  1. 核心原理:通过继承和方法重定义实现。父类引用指向子类对象时,调用被重定义的方法会执行子类的版本,而非父类的版本。

  2. 实现步骤

    • 在父类中定义一个方法
    • 在子类中使用 REDEFINITION 重定义该方法
    • 通过父类引用调用该方法,实际执行的是子类的版本
  3. 动态绑定:ABAP在运行时根据引用所指向的实际对象类型(而非引用变量的声明类型)决定调用哪个版本的方法——这称为动态方法派发(Dynamic Method Dispatch)

  4. 示例

    1
    2
    3
    4
    DATA: lo_animal TYPE REF TO lcl_animal.
    " 实际指向子类对象
    CREATE OBJECT lo_animal TYPE lcl_dog.
    lo_animal->make_sound( ). " 执行 lcl_dog 的版本

题目7 - [应用] 判断向上/向下转换安全性

给定以下类层次结构:lcl_vehicle(父类)-> lcl_car(子类)-> lcl_sports_car(孙类),分析以下代码中哪些赋值操作是安全的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
DATA: lo_vehicle TYPE REF TO lcl_vehicle,
lo_car TYPE REF TO lcl_car,
lo_sports TYPE REF TO lcl_sports_car.

CREATE OBJECT lo_sports.

" 操作1
lo_car = lo_sports.
" 操作2
lo_vehicle = lo_car.
" 操作3
lo_sports ?= lo_vehicle.
" 操作4
lo_car ?= lo_vehicle.
📌查看答案
逐个分析:

操作1:lo_car = lo_sports安全(向上转换)

  • lcl_sports_carlcl_car 的子类,子类引用赋给父类引用始终安全

操作2:lo_vehicle = lo_car安全(向上转换)

  • lcl_carlcl_vehicle 的子类,同样是安全的向上转换
  • 注意:此时 lo_vehicle 实际指向一个 lcl_sports_car 对象

操作3:lo_sports ?= lo_vehicle安全(向下转换,但需要运行时验证)

  • lo_vehicle 当前实际指向 lcl_sports_car 对象,所以向下转换成功
  • 但编译器无法保证这一点,需要使用 ?= 操作符

操作4:lo_car ?= lo_vehicle安全(向下转换,需要运行时验证)

  • lo_vehicle 当前实际指向 lcl_sports_car 对象,而 lcl_sports_car 也是 lcl_car 的子类
  • 转换为 lcl_car 引用也成功

总结:操作1和2在编译时即可确认安全;操作3和4在运行时验证安全(因为实际对象类型满足条件)。但如果 lo_vehicle 指向的是普通的 lcl_vehicle 对象,操作3和4会抛出 cx_sy_move_cast_error 异常。


题目8 - [应用] 多态行为追踪

根据以下代码,追踪方法调用并确定输出结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
CLASS lcl_shape DEFINITION.
PUBLIC SECTION.
METHODS: draw, describe RETURNING VALUE(rv_text) TYPE string.
ENDCLASS.

CLASS lcl_shape IMPLEMENTATION.
METHOD draw. WRITE: / 'Drawing shape'. ENDMETHOD.
METHOD describe. rv_text = 'Generic shape'. ENDMETHOD.
ENDCLASS.

CLASS lcl_circle DEFINITION INHERITING FROM lcl_shape.
PUBLIC SECTION.
METHODS: draw REDEFINITION, describe REDEFINITION.
ENDCLASS.

CLASS lcl_circle IMPLEMENTATION.
METHOD draw. WRITE: / 'Drawing circle'. ENDMETHOD.
METHOD describe. rv_text = 'Circle'. ENDMETHOD.
ENDCLASS.

START-OF-SELECTION.
DATA: lo_shape TYPE REF TO lcl_shape.
DATA: lo_circle TYPE REF TO lcl_circle.

CREATE OBJECT lo_circle.
lo_shape = lo_circle.

lo_shape->draw( ).
lo_circle->draw( ).
WRITE: / lo_shape->describe( ).
📌查看答案
输出结果:
1
2
3
Drawing circle
Drawing circle
Circle

逐步分析:

  1. CREATE OBJECT lo_circle — 创建一个 lcl_circle 对象
  2. lo_shape = lo_circle — 向上转换,lo_shape(父类引用)指向 lcl_circle 对象
  3. lo_shape->draw( ) — 虽然引用类型是 lcl_shape,但实际对象是 lcl_circle,且 draw 被重定义,所以执行 lcl_circle=>draw(),输出 “Drawing circle”
  4. lo_circle->draw( ) — 直接通过子类引用调用,输出 “Drawing circle”
  5. lo_shape->describe( ) — 同理,多态调用执行 lcl_circle=>describe(),返回 “Circle”

核心要点:方法调用由实际对象类型决定,而非引用变量的声明类型。这是多态的本质。


题目9 - [应用] SUPER关键字的使用

以下代码中,子类在重定义的方法中调用了 super->method()。请分析完整的输出结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
CLASS lcl_base DEFINITION.
PUBLIC SECTION.
METHODS: process.
ENDCLASS.

CLASS lcl_base IMPLEMENTATION.
METHOD process.
WRITE: / 'Base: step 1'.
ENDMETHOD.
ENDCLASS.

CLASS lcl_child DEFINITION INHERITING FROM lcl_base.
PUBLIC SECTION.
METHODS: process REDEFINITION.
ENDCLASS.

CLASS lcl_child IMPLEMENTATION.
METHOD process.
super->process( ).
WRITE: / 'Child: step 2'.
ENDMETHOD.
ENDCLASS.

START-OF-SELECTION.
DATA(lo_obj) = NEW lcl_child( ).
lo_obj->process( ).
📌查看答案
输出结果:
1
2
Base: step 1
Child: step 2

分析:

  1. NEW lcl_child( ) — 创建 lcl_child 对象
  2. lo_obj->process( ) — 调用 process 方法,由于是 lcl_child 对象且 process 被重定义,执行 lcl_child 的版本
  3. lcl_child=>process() 中:
    • super->process( ) — 调用父类 lcl_baseprocess 方法,输出 “Base: step 1”
    • WRITE: / 'Child: step 2' — 输出 “Child: step 2”

super 的意义

  • super 代表当前对象的父类部分
  • super->method( ) 用于在重定义方法中调用父类的原始实现
  • 这是一种常见的”扩展而非替换”模式:保留父类行为并添加子类特有的逻辑
  • 注意:super 只能在重定义的方法内部使用,不能在类的外部使用

题目10 - [分析] 继承设计缺陷分析

某项目有以下类设计:

  • lcl_bird(鸟类)有方法 fly()sing()
  • lcl_penguin(企鹅)继承自 lcl_bird

企鹅不会飞,但继承了 fly() 方法。请分析这种设计存在的问题,并提出至少两种解决方案。

📌查看答案

问题分析(违反里氏替换原则):

  • 企鹅是鸟,但不会飞。继承 fly() 方法后,调用 lcl_penguin=>fly() 要么报错,要么什么都不做
  • 如果代码通过 lcl_bird 引用调用 fly() 期望所有鸟都能飞,企鹅会导致不一致行为
  • 这违反了LSP原则:子类对象应该能完全替代父类对象使用而不破坏程序正确性

方案1:重定义方法并抛出异常

1
2
3
4
5
6
7
8
CLASS lcl_penguin DEFINITION INHERITING FROM lcl_bird.
PUBLIC SECTION.
METHODS: fly REDEFINITION.
ENDCLASS.

METHOD fly.
RAISE EXCEPTION TYPE cx_sy_operation_no_support.
ENDMETHOD.
  • 优点:简单直接
  • 缺点:调用者必须处理异常,仍然违反LSP

方案2:重构类层次(推荐)

1
2
3
4
5
6
7
lcl_bird (鸟类)
├── lcl_flying_bird (飞鸟) — 有 fly() 方法
│ ├── lcl_eagle (鹰)
│ └── lcl_sparrow (麻雀)
└── lcl_flightless_bird (不会飞的鸟)
├── lcl_penguin (企鹅)
└── lcl_ostrich (鸵鸟)
  • 优点:符合LSP,类型安全,不会出现不支持的操作
  • 缺点:需要更多类定义

方案3:将 fly() 设为可选行为(接口分离)

  • 定义接口 lif_flyable,包含方法 fly()
  • 只有会飞的鸟类实现该接口
  • 通过接口引用调用 fly(),企鹅不实现此接口

最佳实践总结:继承关系必须满足”is-a”语义且子类能完整替代父类。当子类无法支持父类的某个行为时,应考虑使用接口分离或调整继承层次。


📌模式总结
关键词 答案
继承语法 INHERITING FROM,单继承,自动继承PUBLIC+PROTECTED成员
重定义规则 REDEFINITION,保持签名一致,可用super->调用父类版本
向上转换 子类→父类,始终安全,使用 = 赋值
向下转换 父类→子类,需要 ?= 和运行时检查,配合 IS INSTANCE OF
多态机制 方法调用由实际对象类型决定,非引用声明类型
super关键字 在重定义方法内调用父类原始实现
LSP原则 子类必须能完全替代父类,否则需调整设计