继承与类型转换 练习
继承与类型转换 练习
#practice #inheritance #casting #polymorphism
Related Concepts
📌核心模式
| 关键词 | 答案 |
|---|---|
| 继承 | INHERITING FROM,子类获得父类的属性和方法 |
| 重定义 | REDEFINITION,子类重新实现父类的公有/受保护方法 |
| 向上转换 | ** widening cast,子类引用赋给父类引用,始终安全** |
| 向下转换 | narrowing cast,父类引用赋给子类引用,需要运行时检查 |
| 多态 | 通过重定义实现,同一方法调用在不同子类中有不同行为 |
题目1 - [记忆] 继承的基本概念
请说明ABAP中继承(Inheritance)的基本概念,并写出继承的语法。
📌查看答案
语法:
1 | CLASS lcl_child DEFINITION INHERITING FROM lcl_parent. |
继承的特点:
- 子类自动拥有父类中 PUBLIC 和 PROTECTED 区域的所有成员
- 子类无法直接访问父类的 PRIVATE 成员(但可以通过父类的公有/受保护方法间接访问)
- ABAP只支持单继承(一个类只能有一个直接父类)
- 所有类默认继承自系统根类
OBJECT - 继承关系形成类层次结构(继承树)
题目2 - [记忆] 方法的重定义(REDEFINITION)
请说明方法重定义的规则和限制。
📌查看答案
- 可重定义的范围:只能重定义父类中 PUBLIC SECTION 或 PROTECTED SECTION 的实例方法
- 不可重定义的:
- PRIVATE 方法(子类无法看到)
- CONSTRUCTOR(每个类有自己的构造函数)
- 静态方法(CLASS-METHODS)
- 签名一致:重定义的方法必须保持与父类方法相同的参数接口(参数名、类型、传递方式)
- 语法:
1
METHODS: parent_method REDEFINITION.
- 调用父类版本:在重定义的方法内可以使用
super->parent_method( )调用父类的原始实现 - 目的:实现多态,让子类提供特定于自己的行为
题目3 - [记忆] PROTECTED区域的作用
请解释PROTECTED可见性区域在继承中的特殊作用。
📌查看答案
- 子类可访问:PROTECTED成员可以被类本身及其所有子类(包括间接子类)直接访问
- 外部不可访问:类层次结构之外的代码无法直接访问PROTECTED成员
- 典型用途:
- 供子类重定义的方法的原始实现
- 子类可能需要直接访问的辅助属性或方法
- 为子类提供的”钩子方法”(可重定义的模板方法)
- 与PRIVATE的区别:PRIVATE成员对子类完全不可见,子类只能通过PUBLIC或PROTECTED方法间接访问
- 与PUBLIC的区别:PUBLIC成员对所有人可见,而PROTECTED仅在类层次内部可见
设计建议:供子类使用但不想暴露给外部的成员放在PROTECTED区域。
题目4 - [记忆] 向上转换(Up-cast / Widening Cast)
请解释什么是向上转换(Up-cast),并说明为什么它是安全的。
📌查看答案
语法:
1 | DATA: lo_parent TYPE REF TO lcl_parent, |
为什么安全:
- 子类是父类的扩展,子类对象”是一种”(is-a)父类对象
- 子类包含父类的所有公有接口方法,通过父类引用调用这些方法总是可行的
- 这符合”里氏替换原则”(LSP):子类对象可以替换父类对象使用
效果:
- 通过父类引用只能调用父类中定义的方法(即使实际对象是子类实例)
- 如果子类重定义了某个方法,通过父类引用调用时执行的是子类的重定义版本(多态)
题目5 - [记忆] 向下转换(Down-cast / Narrowing Cast)
请解释什么是向下转换(Down-cast),为什么它可能是危险的,以及ABAP中如何安全地进行向下转换。
📌查看答案
危险性:父类引用可能指向的不是目标子类的对象,此时赋值会导致运行时错误。
语法(使用 ?= 操作符):
1 | DATA: lo_parent TYPE REF TO lcl_parent, |
安全的向下转换方式:
- 使用
CATCH捕获异常:1
2
3
4
5TRY.
lo_child ?= lo_parent.
CATCH cx_sy_move_cast_error.
" 处理转换失败
ENDTRY. - 使用
IS INSTANCE OF检查(推荐):1
2
3IF lo_parent IS INSTANCE OF lcl_child.
lo_child ?= lo_parent.
ENDIF. - 使用内联声明(740 SP8+):
1
2
3IF lo_parent IS INSTANCE OF lcl_child.
DATA(lo_child) = CAST lcl_child( lo_parent ).
ENDIF.
题目6 - [记忆] 多态的实现机制
请说明ABAP中多态(Polymorphism)的实现机制。
📌查看答案
核心原理:通过继承和方法重定义实现。父类引用指向子类对象时,调用被重定义的方法会执行子类的版本,而非父类的版本。
实现步骤:
- 在父类中定义一个方法
- 在子类中使用
REDEFINITION重定义该方法 - 通过父类引用调用该方法,实际执行的是子类的版本
动态绑定:ABAP在运行时根据引用所指向的实际对象类型(而非引用变量的声明类型)决定调用哪个版本的方法——这称为动态方法派发(Dynamic Method Dispatch)
示例:
1
2
3
4DATA: 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_car是lcl_car的子类,子类引用赋给父类引用始终安全
操作2:lo_vehicle = lo_car — 安全(向上转换)
lcl_car是lcl_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 | Drawing circle |
逐步分析:
CREATE OBJECT lo_circle— 创建一个lcl_circle对象lo_shape = lo_circle— 向上转换,lo_shape(父类引用)指向lcl_circle对象lo_shape->draw( )— 虽然引用类型是lcl_shape,但实际对象是lcl_circle,且draw被重定义,所以执行lcl_circle=>draw(),输出 “Drawing circle”lo_circle->draw( )— 直接通过子类引用调用,输出 “Drawing circle”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 | Base: step 1 |
分析:
NEW lcl_child( )— 创建lcl_child对象lo_obj->process( )— 调用process方法,由于是lcl_child对象且process被重定义,执行lcl_child的版本- 在
lcl_child=>process()中:super->process( )— 调用父类lcl_base的process方法,输出 “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 | CLASS lcl_penguin DEFINITION INHERITING FROM lcl_bird. |
- 优点:简单直接
- 缺点:调用者必须处理异常,仍然违反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原则 | 子类必须能完全替代父类,否则需调整设计 |