Mark的技术博客

SAP顾问 | 咨询专家 | IT与AI技术探索者

吴让宇(Mark)

吴让宇(Mark)

SAP顾问 | 咨询专家 | IT与AI技术探索者

吴让宇(Mark)的个人技术博客,专注于SAP系统实施、行业方案咨询、IT技术与AI技术的研究与分享。

SAP Fiori ABAP CDS OData Dify Docker AI LLM

最新文章

继承与类型转换 练习

#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原则 子类必须能完全替代父类,否则需调整设计

继承基础 (★★★)

#abap-objects #inheritance #redefinition

概览表

项目 关键点
继承关系 INHERITING FROM 定义子类
方法重定义 REDEFINITION 改变继承方法的实现(不改签名)
PROTECTED 受保护组件对子类可见
构造器规则 不被继承,子类必须调用 super->constructor( )

泛化/特化关系 (Generalization/Specialization)

继承实现的是 “is-a” 关系:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
"一辆卡车是一种交通工具"

LCL_VEHICLE (父类/超类)
├── make, model, speed (共同属性)
├── set_type( ) (共同方法)
├── display( )

├── LCL_CAR (子类)
│ └── num_seats (特有属性)

├── LCL_TRUCK (子类)
│ └── cargo (特有属性)

└── LCL_BUS (子类)
└── line_name (特有属性)

继承的优势

  • 共同组件只在父类中定义一次
  • 修改父类自动影响所有子类
  • 更好的软件结构
⚠️继承的风险
不能在子类中移除父类的任何组件,只能添加或修改。修改父类语义会影响所有子类,需要非常谨慎。

继承语法

1
2
3
4
5
6
CLASS lcl_truck DEFINITION INHERITING FROM lcl_vehicle.
PUBLIC SECTION.
METHODS: get_cargo RETURNING VALUE(rv_cargo) TYPE i.
PRIVATE SECTION.
DATA: mv_cargo TYPE i.
ENDCLASS.

关键规则:

  • ABAP Objects 不支持多重继承(只能有一个直接父类)
  • 但可以通过接口模拟多重继承
  • 继承是单向关系:子类知道父类,父类不知道子类

方法重定义 (Redefinition)

1
2
3
4
5
6
7
8
9
10
11
12
13
CLASS lcl_car DEFINITION INHERITING FROM lcl_vehicle.
PUBLIC SECTION.
METHODS: display REDEFINITION. " 重定义
ENDCLASS.

CLASS lcl_car IMPLEMENTATION.
METHOD display.
" 调用父类的实现
super->display( ).
" 添加子类特有的输出
WRITE: / 'Seats:', me->mv_num_seats.
ENDMETHOD.
ENDCLASS.

重定义规则:

规则 说明
不能改变签名 方法参数和异常不能修改
可见性不变 父类中的可见性分区必须保持
不能重定义私有方法 PRIVATE SECTION 中的方法不可重定义
使用 super-> 在子类实现中调用父类的原始方法
📌重定义(Redefinition) ≠ 重载(Overloading)
  • ABAP Objects 不支持重载(同名方法多个不同签名)
  • 变通方案:一个方法名使用可选参数的不同组合

构造器在继承中的规则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
" 子类定义了自己的构造器
CLASS lcl_truck DEFINITION INHERITING FROM lcl_vehicle.
PUBLIC SECTION.
METHODS: constructor
IMPORTING
!iv_make TYPE string
!iv_model TYPE string
!iv_cargo TYPE i.
ENDCLASS.

CLASS lcl_truck IMPLEMENTATION.
METHOD constructor.
" 必须先调用父类构造器
super->constructor( iv_make = iv_make iv_model = iv_model ).
mv_cargo = iv_cargo.
ENDMETHOD.
ENDCLASS.

构造器规则总结

情况 行为
子类没有定义构造器 使用父类的构造器
子类定义了构造器 必须调用 super->constructor( )
静态构造器 自动调用父类的静态构造器(无需手动调用)

PROTECTED SECTION

继承引入了新的可见性级别:

可见性 类内部 子类 外部
PUBLIC
PROTECTED
PRIVATE
⚠️声明顺序必须严格
PUBLIC SECTIONPROTECTED SECTIONPRIVATE SECTION

考试/测试模式

场景/关键词 答案
“ABAP支持多重继承吗” 不支持,但可通过接口模拟
“重定义能改变签名吗” 不能
“子类构造器必须做什么” 调用 super->constructor( )
“PROTECTED可见范围” 类自身 + 所有子类
“能重定义私有方法吗” 不能
“重载 vs 重定义” ABAP不支持重载;重定义只改实现不改签名

接口定义与实现 (★★★)

#abap-objects #interface #alias

概览表

项目 关键点
接口定义 INTERFACE ... ENDINTERFACE. 只有公有组件,无实现
实现接口 INTERFACES intf. 必须在 PUBLIC SECTION 中
接口解析 intf~component 使用波浪号操作符
别名 ALIASES name FOR intf~component.

接口的概念

接口与继承的区别主要在于用途不同:

  • 继承: “是一个”关系 (is-a),共享实现
  • 接口: “提供协议”关系 (protocol),定义服务契约
1
2
3
4
5
从技术角度看:
接口 = 不能实例化的超类 + 无实现 + 只有公有组件

从语义角度看:
接口 = 统一的服务协议,不同类可以不同实现

接口的典型使用场景

场景 说明
统一服务协议 多个类提供相同名称和签名的方法
客户端定义协议 使用者定义需要的服务,提供者实现
模拟多重继承 一个类可以实现多个接口

UML中的接口表示

1
2
3
4
5
6
7
8
9
10
11
12
13
┌──────────────┐          ┌──────────────────────┐
│ «interface» │◄─ - - ──│ LCL_HOTEL │
│ LIF_PARTNER │ implements│ (implements LIF_PARTNER)│
├──────────────┤ └──────────────────────┘
│ +display( ) │
│ +check_avail()│
└──────────────┘

│ uses ( - - →«uses» )
┌──────┴──────┐
│LCL_AGENCY │
│ (uses接口) │
└─────────────┘

定义接口

1
2
3
4
5
6
7
8
INTERFACE lif_partner.
METHODS:
display_partner,
check_availability
IMPORTING !iv_date TYPE d
RETURNING VALUE(rv_avail) TYPE abap_bool.
DATA: mv_name TYPE string.
ENDINTERFACE.
📌接口组件全部是公有的
接口中没有可见性分区(没有 PUBLIC/PROTECTED/PRIVATE)
所有接口组件自动为公有

实现接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
CLASS lcl_hotel DEFINITION.
PUBLIC SECTION.
INTERFACES: lif_partner. " 必须在 PUBLIC SECTION
PRIVATE SECTION.
DATA: mv_name TYPE string.
ENDCLASS.

CLASS lcl_hotel IMPLEMENTATION.
METHOD lif_partner~display_partner.
" 实现 display_partner
ENDMETHOD.

METHOD lif_partner~check_availability.
" 实现 check_availability
ENDMETHOD.
ENDCLASS.

实现规则

  1. INTERFACES 语句必须在 PUBLIC SECTION
  2. 接口方法在实现部分中实现
  3. 接口组件使用 intf~component 格式访问

接口解析操作符 (~)

1
2
3
4
5
6
7
8
9
" 在类内部
METHOD some_method.
DATA(name) = lif_partner~mv_name.
lif_partner~display_partner( ).
ENDMETHOD.

" 外部通过对象引用
DATA(lo_hotel) = NEW lcl_hotel( ).
lo_hotel->lif_partner~display_partner( ). " 使用 ~ 访问

别名 (Aliases)

1
2
3
4
5
6
7
CLASS lcl_hotel DEFINITION.
PUBLIC SECTION.
INTERFACES: lif_partner.
ALIASES:
display FOR lif_partner~display_partner,
check FOR lif_partner~check_availability.
ENDCLASS.

使用别名:

1
lo_hotel->display( ).    " 替代 lo_hotel->lif_partner~display_partner( )
💡别名的优势
  • 简化长名称
  • 如果共享组件后来移到接口中,使用别名的代码不需要修改
  • 但别名受类的可见性规则约束

复合接口 (Compound Interfaces)

接口可以继承其他接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
INTERFACE lif_status.
METHODS: get_status RETURNING VALUE(rv_status) TYPE string.
ENDINTERFACE.

INTERFACE lif_serializable.
METHODS: serialize RETURNING VALUE(rv_data) TYPE xstring.
ENDINTERFACE.

" 复合接口
INTERFACE lif_persistent.
INTERFACES: lif_status, lif_serializable.
METHODS: save.
ENDINTERFACE.

考试/测试模式

场景/关键词 答案
“接口有PRIVATE SECTION吗” 没有,所有组件都是公有的
“INTERFACES放在哪里” 必须在 PUBLIC SECTION
“~操作符的含义” 接口解析操作符,intf~component
“别名受什么约束” 受类的可见性规则约束
“接口可以被实例化吗” 不能
“接口和继承的技术区别” 接口无实现部分,所有组件公有

单例、工厂与友元 (★★)

#abap-objects #singleton #factory #friendship

概览表

项目 关键点
单例模式 确保类只有一个实例
工厂模式 通过方法创建对象,隐藏实例化细节
友元关系 允许特定类访问私有组件
导航方法 返回关联对象引用的方法
构造器可见性 可以限制为 PRIVATE/PROTECTED

单例模式 (Singleton Pattern)

确保一个类只有一个实例并提供全局访问点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
CLASS lcl_database DEFINITION CREATE PRIVATE.
PUBLIC SECTION.
CLASS-METHODS: get_instance
RETURNING VALUE(ro_inst) TYPE REF TO lcl_database.
PRIVATE SECTION.
CLASS-DATA: mo_instance TYPE REF TO lcl_database.
METHODS: constructor.
ENDCLASS.

CLASS lcl_database IMPLEMENTATION.
METHOD get_instance.
IF mo_instance IS NOT BOUND.
CREATE OBJECT mo_instance.
ENDIF.
ro_inst = mo_instance.
ENDMETHOD.
METHOD constructor.
" 初始化
ENDMETHOD.
ENDCLASS.

实现要点

  1. 构造器设为 PRIVATE (CREATE PRIVATE)
  2. 静态属性保存唯一实例
  3. 静态方法 get_instance( ) 提供访问点
  4. 惰性初始化 — 首次调用时创建
1
2
调用者1: DATA(db) = lcl_database=>get_instance( ). ─→ [实例A]
调用者2: DATA(db2) = lcl_database=>get_instance( ). ─→ [实例A] ← 同一个!

工厂方法/工厂类 (Factory Pattern)

将对象创建逻辑封装到专门的方法或类中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
CLASS lcl_vehicle_factory DEFINITION.
PUBLIC SECTION.
METHODS: create_vehicle
IMPORTING !iv_type TYPE string
RETURNING VALUE(ro_veh) TYPE REF TO lcl_vehicle.
ENDCLASS.

CLASS lcl_vehicle_factory IMPLEMENTATION.
METHOD create_vehicle.
CASE iv_type.
WHEN 'CAR'.
ro_veh = NEW lcl_car( ).
WHEN 'TRUCK'.
ro_veh = NEW lcl_truck( ).
WHEN 'BUS'.
ro_veh = NEW lcl_bus( ).
ENDCASE.
ENDMETHOD.
ENDCLASS.

工厂模式的优势

优势 说明
隐藏实例化细节 调用者不需要知道具体创建哪个类
集中创建逻辑 创建逻辑变化只需改工厂
配合构造器可见性限制 只能通过工厂创建对象

构造器可见性

1
2
3
" 限制构造器的可见性
CLASS lcl_car DEFINITION CREATE PRIVATE FRIENDS lcl_vehicle_factory.
" → 只有 lcl_vehicle_factory 能创建 lcl_car 实例

友元关系 (Friendship)

允许指定的类访问私有组件

1
2
3
4
CLASS lcl_vehicle DEFINITION FRIENDS lcl_vehicle_factory.
PRIVATE SECTION.
DATA: mv_serial TYPE string.
ENDCLASS.

友元规则

规则 说明
单向 A FRIENDS B → B可访问A的私有组件,反之不行
不继承 友元关系不传递给子类
可在定义时指定 CLASS ... DEFINITION FRIENDS friend_class.
⚠️友元是封装的例外
友元破坏了封装原则,应谨慎使用。仅在确实需要时使用(如工厂模式、测试类)。

导航方法 (Navigation Method)

返回关联对象引用的方法:

1
2
3
4
5
6
7
8
CLASS lcl_rental DEFINITION.
PUBLIC SECTION.
METHODS: get_vehicle
IMPORTING !iv_index TYPE i
RETURNING VALUE(ro_veh) TYPE REF TO lcl_vehicle.
PRIVATE SECTION.
DATA: mt_vehicles TYPE TABLE OF REF TO lcl_vehicle.
ENDCLASS.

链式方法调用 (7.0 EhP2+)

1
2
3
4
5
6
7
" 以前: 需要中间变量
DATA(lo_veh) = lo_rental->get_vehicle( 1 ).
DATA(lo_motor) = lo_veh->get_motor( ).
DATA(lv_power) = lo_motor->get_power( ).

" 现在: 直接链式调用
DATA(lv_power) = lo_rental->get_vehicle( 1 )->get_motor( )->get_power( ).

内表中对象引用的访问

1
2
3
4
5
6
7
8
9
" 使用公有只读属性作为键
READ TABLE mt_vehicles INTO DATA(lo_veh)
WITH KEY table_line->make = 'BMW'.

" 或使用 WHERE 子句
LOOP AT mt_vehicles INTO DATA(lo_veh)
WHERE table_line->make = 'BMW'.
" ...
ENDLOOP.
💡TABLE_LINE
当内表行类型不是结构而是基本类型(如 REF TO)时,使用 TABLE_LINE 作为泛型列名。

考试/测试模式

场景/关键词 答案
“单例模式核心” CREATE PRIVATE + 静态实例 + get_instance( )
“工厂方法的作用” 封装对象创建逻辑
“友元关系方向” 单向,不传递
“链式调用要求” 7.0 EhP2+ 支持
“构造器可见性” 可限制为 PRIVATE/PROTECTED

ALV Grid 与 BAdI (★★)

#abap-objects #global-class #alv-grid #badi

概览表

项目 关键点
ALV Grid Control 使用全局类 CL_GUI_ALV_GRID 显示表格数据
事件处理 处理 ALV Grid 的事件(如双击)
BAdI 基于接口的增强技术

ALV Grid Control

ALV Grid 是 SAP 提供的全局类的典型应用示例:

核心类

用途
CL_GUI_ALV_GRID ALV Grid 控件
CL_GUI_CUSTOM_CONTAINER 自定义容器

使用步骤

1
2
3
4
5
6
1. 创建屏幕 (Screen) 和容器区域
2. 创建 CL_GUI_CUSTOM_CONTAINER 实例
3. 创建 CL_GUI_ALV_GRID 实例(传入容器)
4. 准备字段目录 (Field Catalog) 和数据表
5. 调用 set_table_for_first_display( )
6. 处理交互事件(如双击)

基本代码结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
DATA: go_container TYPE REF TO cl_gui_custom_container,
go_grid TYPE REF TO cl_gui_alv_grid.

" PBO 模块中
IF go_container IS NOT BOUND.
CREATE OBJECT go_container
EXPORTING container_name = 'CC_ALV'.

CREATE OBJECT go_grid
EXPORTING i_parent = go_container.

go_grid->set_table_for_first_display(
EXPORTING i_structure_name = 'SFLIGHT'
CHANGING it_outtab = gt_flights ).
ENDIF.

处理 ALV 事件(双击示例)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
CLASS lcl_event_handler DEFINITION.
PUBLIC SECTION.
METHODS: on_double_click
FOR EVENT double_click OF cl_gui_alv_grid
IMPORTING e_row e_column.
ENDCLASS.

CLASS lcl_event_handler IMPLEMENTATION.
METHOD on_double_click.
" 处理双击事件
READ TABLE gt_flights INTO DATA(ls_flight) INDEX e_row-index.
MESSAGE s000(oo) WITH 'Selected:' ls_flight-carrid.
ENDMETHOD.
ENDCLASS.

" 注册事件处理
DATA: go_handler TYPE REF TO lcl_event_handler.
CREATE OBJECT go_handler.
SET HANDLER go_handler->on_double_click FOR go_grid.

BAdI (Business Add-In) 技术

BAdI 是基于接口的增强技术:

BAdI 的核心概念

1
2
3
4
5
6
SAP定义接口 (ZIF_BADI_<name>)

├── SAP标准实现(可选)

└── 客户实现 (ZCL_BADI_IMPL)
└── 实现接口方法

BAdI 与普通接口的区别

特性 普通接口 BAdI
定义者 开发者 SAP或开发者
实现者 同一开发者 可能是不同的团队
发现机制 直接引用 通过BAdI Spot查找
多实现 一个类一个实现 可能有多个实现

使用BAdI的步骤

  1. 定义 BAdI 接口(在 Class Builder 中使用 BAdI Builder)
  2. 在程序中获取BAdI实例:GET BADI lo_badi.
  3. 调用BAdI方法:CALL BADI lo_badi->method.
  4. 创建实现类(增强实施)

考试/测试模式

场景/关键词 答案
“ALV Grid的核心类” CL_GUI_ALV_GRID
“ALV事件处理” 定义处理器类 + SET HANDLER FOR go_grid
“BAdI基于什么” 接口
“BAdI vs 普通接口” BAdI支持多实现和动态发现

接口多态 (★★★)

#abap-objects #interface #polymorphism #casting

概览表

项目 关键点
接口引用 TYPE REF TO intf 指向实现接口的类实例
Up-cast 对象引用 → 接口引用,隐式安全
Down-cast 接口引用 → 对象引用,显式 ?=
多态调用 通过接口引用统一调用不同实现

接口引用

1
DATA: lo_partner TYPE REF TO lif_partner.  " 接口引用
  • 接口引用只能指向实现了该接口的类的实例
  • 通过接口引用只能访问接口定义的组件
  • 不能访问类的特有组件

Up-cast with Interfaces

1
2
3
4
5
6
7
8
9
10
11
12
DATA: lo_hotel  TYPE REF TO lcl_hotel,
lo_partner TYPE REF TO lif_partner.

CREATE OBJECT lo_hotel.
lo_partner = lo_hotel. " Up-cast: 隐式

" 现在通过 lo_partner 只能访问 lif_partner 的组件:
lo_partner->display_partner( ).
lo_partner->check_availability( iv_date = sy-datum ).

" 以下不行:
" lo_partner->get_hotel_name( ). " 不是接口方法!
💡注意波浪号操作符的省略
使用接口引用时不需要 ~ 操作符:
  • 对象引用: lo_hotel->lif_partner~display_partner( )
  • 接口引用: lo_partner->display_partner( ) ← 更简洁

泛型访问的典型模式

1
2
3
4
5
6
7
8
9
10
11
12
13
" 使用接口引用作为内表行类型
DATA: lt_partners TYPE TABLE OF REF TO lif_partner.

" 收集不同类型的业务伙伴
APPEND NEW lcl_hotel( ) TO lt_partners. " 自动 up-cast
APPEND NEW lcl_rental( ) TO lt_partners. " 自动 up-cast
APPEND NEW lcl_carrier( ) TO lt_partners. " 自动 up-cast

" 统一处理 — 多态!
LOOP AT lt_partners INTO DATA(lo_p).
lo_p->display_partner( ).
lo_p->check_availability( iv_date = sy-datum ).
ENDLOOP.

Down-cast with Interfaces

当需要访问实现类的特有组件时:

1
2
3
4
5
6
7
8
DATA: lo_hotel   TYPE REF TO lcl_hotel,
lo_partner TYPE REF TO lif_partner.

" ... lo_partner 指向某个对象 ...

" Down-cast 回具体类
lo_hotel ?= lo_partner. " 如果实际对象是 lcl_hotel 则成功
lo_hotel->get_hotel_name( ). " 现在可以访问特有方法

接口 vs 继承 — 多态对比

特性 继承多态 接口多态
基础 父类引用 接口引用
引用类型 TYPE REF TO superclass TYPE REF TO interface
可访问组件 父类定义的 接口定义的
Up-cast 子类→父类 对象→接口
典型场景 同一族的对象 提供相同服务的不同类

方法参数使用接口引用

1
2
3
4
5
6
7
CLASS lcl_travel_agency DEFINITION.
PUBLIC SECTION.
METHODS:
add_partner IMPORTING io_partner TYPE REF TO lif_partner.
PRIVATE SECTION.
DATA: mt_partners TYPE TABLE OF REF TO lif_partner.
ENDCLASS.
📌接口引用作为参数
这是面向接口编程的核心:方法不依赖具体类,只依赖接口。
新的实现类只要实现了接口,就可以直接传入。

考试/测试模式

场景/关键词 答案
“接口引用能指向什么” 任何实现了该接口的类的实例
“接口引用能访问什么” 只能访问接口定义的组件
“接口多态的用途” 统一处理实现了相同接口的不同类的对象
“何时需要down-cast” 需要访问实现类特有组件时

抽象类与最终类 (★★★)

#abap-objects #abstract-class #final-class

概览表

项目 关键点
抽象类 不能实例化,作为父类使用
抽象方法 只有声明没有实现,子类必须实现
最终类 不能被继承
最终方法 不能被重定义

抽象类 (Abstract Class)

1
2
3
4
5
6
CLASS lcl_vehicle DEFINITION ABSTRACT.
PUBLIC SECTION.
METHODS: display ABSTRACT.
" 可以有普通方法
METHODS: get_speed RETURNING VALUE(rv_speed) TYPE i.
ENDCLASS.

特性

规则 说明
不能实例化 CREATE OBJECT 会报错
可以有抽象方法 只有声明,没有实现
可以有普通方法 包含完整实现
子类必须实现抽象方法 除非子类也是抽象类
静态方法不能是抽象的 因为静态方法不能被重定义

使用场景

1
2
3
4
5
6
7
8
9
10
11
12
13
LCL_VEHICLE (抽象类)
│ display( ) → 抽象方法
│ get_speed( ) → 有实现

├── LCL_CAR
│ display( ) → 必须实现

└── LCL_TRUCK
display( ) → 必须实现

" 不能: CREATE OBJECT lo_vehicle TYPE lcl_vehicle. ← 错误!
" 可以: CREATE OBJECT lo_car TYPE lcl_car. ← 正确
" 可以: DATA: lo_v TYPE REF TO lcl_vehicle = lo_car. ← up-cast
💡抽象类引用可以用于多态
虽然不能创建抽象类的实例,但可以使用抽象类的引用通过 up-cast 指向子类实例,实现多态调用。

抽象方法 (Abstract Method)

1
2
METHODS: calculate_price ABSTRACT
RETURNING VALUE(rv_price) TYPE decfloat16.
  • 只有声明,没有实现
  • 子类必须实现(除非子类也是抽象类)
  • 不能在 PRIVATE SECTION 中声明

最终类 (Final Class)

1
2
3
4
5
6
CLASS lcl_math_utils DEFINITION FINAL.
PUBLIC SECTION.
CLASS-METHODS: calculate
IMPORTING !iv_value TYPE i
RETURNING VALUE(rv_result) TYPE i.
ENDCLASS.

特性

规则 说明
不能被继承 没有子类
所有方法隐式为最终 不能重定义不存在的继承
可以正常实例化 和普通类一样

同时抽象和最终

  • 理论上可以但只应包含静态组件
  • 没有实际继承价值

最终方法 (Final Method)

1
2
3
4
5
CLASS lcl_vehicle DEFINITION.
PUBLIC SECTION.
METHODS: get_speed FINAL " 不能在子类中重定义
RETURNING VALUE(rv_speed) TYPE i.
ENDCLASS.
  • 该方法不能在任何子类中被重定义
  • 但子类仍然继承该方法(只是不能改)

抽象 vs 最终 对比

特性 抽象 (ABSTRACT) 最终 (FINAL)
类级别 不能实例化 不能被继承
方法级别 子类必须实现 子类不能重定义
用途 定义框架/骨架 防止修改/扩展
实例化
被继承

考试/测试模式

场景/关键词 答案
“抽象类能实例化吗” 不能
“抽象方法有实现吗” 没有,子类必须实现
“最终类能被继承吗” 不能
“静态方法能是抽象的吗” 不能
“抽象类引用能做什么” 可以用于多态(up-cast到子类实例)

特殊OO技术 练习

#practice #abstract #final #singleton #factory #friendship #abap-objects

📌核心模式 (点击展开)
关键词 答案
抽象类 CLASS ... DEFINITION ABSTRACT
抽象方法 METHODS ... ABSTRACT,无实现
最终类 CLASS ... DEFINITION FINAL,不能被继承
最终方法 METHODS ... FINAL,不能被重写
单例模式 CREATE PRIVATE + CLASS-DATA + GET_INSTANCE
工厂模式 根据输入返回不同子类实例
友元 CLASS ... DEFINITION FRIENDS ...

题目1 - 抽象类的定义与限制 [记忆]

什么是抽象类(Abstract Class)?在 ABAP 中如何定义?抽象类有哪些限制?

📌查看答案
抽象类定义:抽象类是不能直接实例化的类,只能作为其他类的基类使用。

定义语法

1
2
3
4
5
CLASS lcl_animal DEFINITION ABSTRACT.
PUBLIC SECTION.
METHODS: make_sound ABSTRACT,
get_name RETURNING VALUE(rv_name) TYPE string.
ENDCLASS.

限制

  • 不能使用 CREATE OBJECT 直接创建抽象类的实例
  • 抽象类中的抽象方法(ABSTRACT)没有实现,必须在具体子类中重写
  • 抽象方法只能在抽象类中定义
  • 抽象类可以同时包含抽象方法和具体方法
  • 子类必须实现所有抽象方法,否则子类也必须是抽象类

使用场景:定义通用接口和部分默认实现,强制子类提供特定行为。


题目2 - Final 类和 Final 方法 [记忆]

什么是最终类(Final Class)和最终方法(Final Method)?它们的设计目的是什么?

📌查看答案
最终类(Final Class)
1
2
3
4
CLASS lcl_utility DEFINITION FINAL.
PUBLIC SECTION.
CLASS-METHODS: convert_string.
ENDCLASS.
  • 整个类不能被继承
  • 最终类中的所有方法隐式为 final

最终方法(Final Method)

1
2
3
4
CLASS lcl_base DEFINITION.
PUBLIC SECTION.
METHODS: get_id FINAL.
ENDCLASS.
  • 该方法不能在子类中被重写(redefined)
  • 最终方法可以在非最终类中定义

设计目的

  1. 安全性:防止子类修改关键行为,确保一致性
  2. 稳定性:保证方法的实现不会被覆盖
  3. 性能:编译器可以对 final 调用进行优化
  4. 设计约束:明确表达设计意图——该扩展点已关闭

注意:ABSTRACTFINAL 不能同时用于同一个方法(矛盾)。但抽象类可以是 final 的(无实际意义)。


题目3 - 单例模式实现 [记忆]

请描述单例模式(Singleton Pattern)在 ABAP 中的完整实现方式,包括类定义和实现。

📌查看答案
单例模式 ABAP 实现
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_singleton DEFINITION
CREATE PRIVATE. " 禁止外部实例化

PUBLIC SECTION.
CLASS-METHODS: get_instance
RETURNING VALUE(ro_instance) TYPE REF TO lcl_singleton.
METHODS: do_something.

PRIVATE SECTION.
CLASS-DATA: mo_instance TYPE REF TO lcl_singleton.
METHODS: constructor.

ENDCLASS.

CLASS lcl_singleton IMPLEMENTATION.
METHOD get_instance.
IF mo_instance IS NOT BOUND.
mo_instance = NEW lcl_singleton( ).
ENDIF.
ro_instance = mo_instance.
ENDMETHOD.

METHOD constructor.
" 初始化逻辑
ENDMETHOD.

METHOD do_something.
" 业务逻辑
ENDMETHOD.
ENDCLASS.

核心要素

  1. CREATE PRIVATE — 外部无法直接 CREATE OBJECT
  2. CLASS-DATA mo_instance — 静态属性保存唯一实例
  3. get_instance( ) — 静态工厂方法,延迟创建并返回实例
  4. 构造函数为 PRIVATE,防止子类和外部调用

线程安全:ABAP 是单线程执行的(每个对话步骤),所以不需要额外的线程同步机制。


题目4 - 工厂模式 [记忆]

什么是工厂模式(Factory Pattern)?在 ABAP 中如何利用继承和接口实现工厂方法?

📌查看答案
工厂模式:定义一个创建对象的接口,让子类决定实例化哪个类。将实例化逻辑与使用逻辑分离。

ABAP 实现示例

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
31
32
33
34
35
36
37
" 产品接口
INTERFACE lif_document.
METHODS: print,
get_type RETURNING VALUE(rv_type) TYPE string.
ENDINTERFACE.

" 具体产品
CLASS lcl_pdf_doc DEFINITION.
PUBLIC SECTION.
INTERFACES lif_document.
ENDCLASS.

CLASS lcl_word_doc DEFINITION.
PUBLIC SECTION.
INTERFACES lif_document.
ENDCLASS.

" 工厂类
CLASS lcl_doc_factory DEFINITION.
PUBLIC SECTION.
CLASS-METHODS: create_document
IMPORTING iv_type TYPE string
RETURNING VALUE(ro_doc) TYPE REF TO lif_document.
ENDCLASS.

CLASS lcl_doc_factory IMPLEMENTATION.
METHOD create_document.
CASE iv_type.
WHEN 'PDF'.
ro_doc = NEW lcl_pdf_doc( ).
WHEN 'WORD'.
ro_doc = NEW lcl_word_doc( ).
WHEN OTHERS.
" 可抛出异常
ENDCASE.
ENDMETHOD.
ENDCLASS.

使用

1
2
DATA(lo_doc) = lcl_doc_factory=>create_document( 'PDF' ).
lo_doc->print( ). " 多态调用

优点:调用者不需要知道具体类名,只需通过工厂获取对象。


题目5 - 友元关系 (Friendship) [记忆]

什么是 ABAP 中的友元关系(Friendship)?如何定义和使用?它对封装性有什么影响?

📌查看答案
友元关系定义:一个类授予另一个类(或接口)访问其私有(PRIVATE)和受保护(PROTECTED)成员的权限。

定义语法

1
2
3
4
5
6
7
CLASS lcl_internal DEFINITION
FRIENDS lcl_manager. " 授予 lcl_manager 友元权限

PRIVATE SECTION.
DATA: mv_secret TYPE string.
METHODS: private_method.
ENDCLASS.

关键规则

  1. 单向性:友元关系是单向的。如果 A 是 B 的朋友,B 不是 A 的朋友(除非显式声明)
  2. 不可继承:友元关系不能被子类继承
  3. 传递性:如果 A 是 B 的朋友,B 是 C 的朋友,A 不是 C 的朋友
  4. 接口友元:可以将接口声明为友元,实现该接口的类获得友元权限

对封装的影响

  • 友元破坏了严格的封装性,应谨慎使用
  • 适用场景:紧密协作的类之间(如单元测试类访问被测类的私有成员)
  • 不应作为常规设计手段,仅在必要时使用

全局类中的友元

1
2
CLASS zcl_my_class DEFINITION
FRIENDS zcl_test_class. " 允许测试类访问私有成员

题目6 - 抽象与最终组合分析 [分析]

为什么 ABAP 不允许一个方法同时声明为 ABSTRACT 和 FINAL?在什么情况下你会同时使用抽象类和最终类?请分析这种组合的设计意义。

📌查看答案
ABSTRACT + FINAL 矛盾原因
  • ABSTRACT 意味着方法 没有实现,必须在子类中重写
  • FINAL 意味着方法 不能在子类中重写
  • 这两者直接矛盾:一个要求子类必须重写,另一个禁止子类重写

抽象类 + 最终类的组合场景

虽然在方法级别矛盾,但在类级别可以有意义地组合:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
" 模板方法模式中的典型应用
CLASS lcl_template DEFINITION ABSTRACT.
PUBLIC SECTION.
METHODS: template_method FINAL. " 最终方法:固定算法骨架
METHODS: step_one ABSTRACT. " 抽象方法:由子类实现
METHODS: step_two ABSTRACT.
ENDCLASS.

CLASS lcl_template IMPLEMENTATION.
METHOD template_method.
" 固定流程——子类不能改变调用顺序
me->step_one( ).
me->step_two( ).
ENDMETHOD.
ENDCLASS.

设计意义

  1. 模板方法模式:抽象类定义算法骨架(FINAL),子类填充具体步骤(ABSTRACT)
  2. 开放封闭原则:对扩展开放(通过抽象方法),对修改封闭(通过最终方法)
  3. 控制反转:基类控制整体流程,子类只负责具体实现

题目7 - 导航方法 (Navigation Methods) [记忆]

什么是导航方法(Navigation Methods)?它在面向对象设计中有什么作用?请举例说明。

📌查看答案
导航方法定义:导航方法是指通过对象间的关联关系,从一个对象获取到另一个相关对象的引用的方法。

作用

  • 封装对象间的关系,外部不需要了解内部结构
  • 实现对象图中的遍历
  • 隐藏集合或映射的内部实现

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
CLASS lcl_order DEFINITION.
PUBLIC SECTION.
METHODS:
" 导航方法:从订单导航到客户
get_customer RETURNING VALUE(ro_customer) TYPE REF TO lcl_customer,
" 导航方法:从订单导航到订单行项目
get_items RETURNING VALUE(rt_items) TYPE lcl_item=>tt_items,
" 导航方法:根据索引获取特定行项目
get_item
IMPORTING iv_index TYPE i
RETURNING VALUE(ro_item) TYPE REF TO lcl_item.
PRIVATE SECTION.
DATA: mo_customer TYPE REF TO lcl_customer,
mt_items TYPE STANDARD TABLE OF REF TO lcl_item.
ENDCLASS.

使用方式

1
2
3
4
5
" 链式导航
DATA(lv_name) = lo_order->get_customer( )->get_name( ).

" 导航到集合
DATA(lt_items) = lo_order->get_items( ).

导航方法是面向对象设计中的基本模式,体现了”告诉对象做什么,而不是向对象要数据”的原则。


题目8 - 设计模式综合应用 [应用]

请设计一个日志系统,要求:1) 使用单例模式确保只有一个日志管理器;2) 使用工厂模式根据日志级别创建不同类型的日志写入器(文件、数据库);3) 使用抽象类定义日志写入器的公共接口。

📌查看答案
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
" ---- 抽象日志写入器 ----
CLASS lcl_log_writer DEFINITION ABSTRACT.
PUBLIC SECTION.
METHODS: write_log ABSTRACT
IMPORTING iv_message TYPE string
iv_level TYPE char1.
METHODS: get_type ABSTRACT
RETURNING VALUE(rv_type) TYPE string.
ENDCLASS.

" ---- 文件写入器 ----
CLASS lcl_file_writer DEFINITION INHERITING FROM lcl_log_writer.
PUBLIC SECTION.
METHODS: write_log REDEFINITION,
get_type REDEFINITION.
ENDCLASS.
CLASS lcl_file_writer IMPLEMENTATION.
METHOD write_log.
" 写入文件的实现
WRITE: / '[FILE]', iv_level, iv_message.
ENDMETHOD.
METHOD get_type. rv_type = 'FILE'. ENDMETHOD.
ENDCLASS.

" ---- 数据库写入器 ----
CLASS lcl_db_writer DEFINITION INHERITING FROM lcl_log_writer.
PUBLIC SECTION.
METHODS: write_log REDEFINITION,
get_type REDEFINITION.
ENDCLASS.
CLASS lcl_db_writer IMPLEMENTATION.
METHOD write_log.
" 写入数据库的实现
WRITE: / '[DB]', iv_level, iv_message.
ENDMETHOD.
METHOD get_type. rv_type = 'DB'. ENDMETHOD.
ENDCLASS.

" ---- 工厂 ----
CLASS lcl_writer_factory DEFINITION.
PUBLIC SECTION.
CLASS-METHODS: create
IMPORTING iv_type TYPE string
RETURNING VALUE(ro_writer) TYPE REF TO lcl_log_writer.
ENDCLASS.
CLASS lcl_writer_factory IMPLEMENTATION.
METHOD create.
CASE iv_type.
WHEN 'FILE'. ro_writer = NEW lcl_file_writer( ).
WHEN 'DB'. ro_writer = NEW lcl_db_writer( ).
ENDCASE.
ENDMETHOD.
ENDCLASS.

" ---- 单例日志管理器 ----
CLASS lcl_log_manager DEFINITION CREATE PRIVATE.
PUBLIC SECTION.
CLASS-METHODS: get_instance
RETURNING VALUE(ro_mgr) TYPE REF TO lcl_log_manager.
METHODS: log
IMPORTING iv_message TYPE string
iv_level TYPE char1 DEFAULT 'I'.
PRIVATE SECTION.
CLASS-DATA: mo_instance TYPE REF TO lcl_log_manager.
DATA: mo_writer TYPE REF TO lcl_log_writer.
METHODS: constructor.
ENDCLASS.
CLASS lcl_log_manager IMPLEMENTATION.
METHOD get_instance.
IF mo_instance IS NOT BOUND.
mo_instance = NEW lcl_log_manager( ).
ENDIF.
ro_mgr = mo_instance.
ENDMETHOD.
METHOD constructor.
mo_writer = lcl_writer_factory=>create( 'FILE' ).
ENDMETHOD.
METHOD log.
mo_writer->write_log( iv_message = iv_message iv_level = iv_level ).
ENDMETHOD.
ENDCLASS.

" ---- 使用 ----
START-OF-SELECTION.
lcl_log_manager=>get_instance( )->log(
iv_message = '系统启动' iv_level = 'I' ).

📌模式总结 (点击展开)
关键词 答案
抽象类 DEFINITION ABSTRACT,不能实例化
抽象方法 METHODS ... ABSTRACT,无实现,子类必须重写
最终类 DEFINITION FINAL,不能被继承
最终方法 METHODS ... FINAL,不能被重写
单例模式 CREATE PRIVATE + 静态实例 + get_instance
工厂模式 集中创建逻辑,返回接口引用
友元 FRIENDS 关键字,单向授予私有访问权
导航方法 通过方法返回关联对象引用
模板方法 FINAL 方法定义骨架,ABSTRACT 方法定义步骤
ABSTRACT+FINAL 矛盾 方法级别不可共存,类级别可组合使用

全局类与接口 练习

#practice #global-classes #se24 #alv #badi #abap-objects

📌核心模式 (点击展开)
关键词 答案
Class Builder 事务 SE24,创建和维护全局类
全局类 vs 局部类 全局类存储在 ABAP Repository,所有程序可用
ALV Grid CL_GUI_ALV_GRID,基于控件的面向对象列表
BAdI 基于接口的增强技术,使用 GET BADI / CALL BADI

题目1 - 全局类与局部类的区别 [记忆]

请列出全局类(Global Class)和局部类(Local Class)的至少五个主要区别。

📌查看答案
特征 全局类 局部类
定义位置 ABAP Repository(Class Builder) 程序内部(ABAP 程序源码中)
可见性 所有 ABAP 程序可用 仅在定义它的程序中可见
创建工具 事务 SE24/SE80 在代码编辑器中手动编码
存储 存储在 SAP 系统的类库中 嵌入在具体程序中
命名规范 以 Z/Y 开头(自定义) 任意名称(通常 LCL_ 前缀)
类型池 可使用 Type Groups 不能定义自己的 Type Groups
测试 可创建全局测试类(ABAP Unit) 测试较为受限
继承 可以继承其他全局类 可以继承全局类,但自身不可被全局类继承
传输 通过 CTS 传输 随程序一起传输

题目2 - Class Builder (SE24) 功能 [记忆]

使用事务 SE24 (Class Builder) 创建全局类时,主要有哪些步骤和配置选项?

📌查看答案
创建步骤
  1. 执行事务 SE24,输入类名(如 ZCL_MY_CLASS),点击创建
  2. 填写基本信息:描述(Description)、瞬时性(Instantiation)
  3. 选择类类型(Class、Interface、Exception Class 等)

主要配置选项

  • Properties 标签页:类的基本属性、瞬时性设置(PUBLIC/PROTECTED/PRIVATE/ABSTRACT)
  • Interfaces 标签页:添加实现的全局接口
  • Friends 标签页:声明友元类
  • Attributes 标签页:定义实例和静态属性
  • Methods 标签页:定义方法,可设置参数、异常、方法类型
  • Events 标签页:定义事件
  • Types 标签页:定义公共类型

瞬时性(Instantiation)选项

  • PUBLIC — 任何用户可创建实例
  • PROTECTED — 仅子类可创建
  • PRIVATE — 仅自身可创建(适合单例模式)
  • ABSTRACT — 不能实例化

题目3 - 全局接口的使用 [记忆]

在 ABAP 中如何定义和使用全局接口?全局接口与局部接口在定义和实现上有什么区别?

📌查看答案
定义全局接口
  • 在 SE24 中选择类型为「Interface」,命名以 ZIF_ 开头
  • 在接口中声明方法、属性、事件,但不包含实现

使用全局接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
" 在 SE24 的 Interfaces 标签页中输入接口名
" 或在代码中:
INTERFACE zif_my_interface.
METHODS: calculate
IMPORTING iv_value TYPE i
RETURNING VALUE(rv_result) TYPE i.
ENDINTERFACE.

" 全局类实现接口
CLASS zcl_my_class DEFINITION
PUBLIC CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES zif_my_interface.
ENDCLASS.

区别

  • 全局接口存储在 Repository 中,可被任何全局类或局部类实现
  • 局部接口只能在定义它的程序中使用
  • 全局接口可用于 BAdI 定义和跨组件通信
  • 全局接口的别名(ALIAS)可以在 SE24 中配置

题目4 - ALV Grid Control 基本架构 [记忆]

描述 CL_GUI_ALV_GRID 的基本架构。使用 ALV Grid 显示数据需要哪些关键对象和步骤?

📌查看答案
ALV Grid 架构中的关键对象
  1. 容器对象CL_GUI_CUSTOM_CONTAINER(绑定到屏幕上的 Custom Control)或 CL_GUI_DOCKING_CONTAINER
  2. ALV Grid 对象CL_GUI_ALV_GRID
  3. 字段目录(Field Catalog):类型 LVC_T_FCAT,定义列属性
  4. 布局结构(Layout):类型 LVC_S_LAYO,定义整体显示属性
  5. 数据表:标准内表,存储要显示的数据

基本步骤

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
" 1. 创建容器
DATA(lo_container) = NEW cl_gui_custom_container(
container_name = 'CC_ALV' ).

" 2. 创建 ALV Grid
DATA(lo_grid) = NEW cl_gui_alv_grid(
i_parent = lo_container ).

" 3. 准备字段目录和数据表
" ... (填充 field catalog 和 data table)

" 4. 显示数据
lo_grid->set_table_for_first_display(
CHANGING
it_outtab = lt_data
it_fieldcatalog = lt_fcat ).

需要创建一个带有 Custom Control 的屏幕(Screen),PBO 中初始化 ALV。


题目5 - ALV Grid 事件处理 [记忆]

ALV Grid Control 提供了哪些常用事件?如何注册处理这些事件?

📌查看答案
ALV Grid 常用事件
事件名 触发时机 常见用途
DATA_CHANGED 单元格数据被修改 数据验证
DATA_CHANGED_FINISHED 数据修改完成 更新后处理
DOUBLE_CLICK 用户双击单元格 导航到明细
HOTSPOT_CLICK 点击热点列 链接式导航
TOOLBAR ALV 工具栏初始化 添加自定义按钮
USER_COMMAND 自定义按钮被点击 处理自定义功能
MENU_BUTTON 菜单按钮点击 下拉菜单处理
ONF4 F4 帮助请求 自定义搜索帮助

注册事件处理

1
2
3
4
5
6
7
" 1. 定义处理方法
METHODS on_double_click
FOR EVENT double_click OF cl_gui_alv_grid
IMPORTING e_row e_column.

" 2. 注册处理程序
SET HANDLER lo_handler->on_double_click FOR lo_grid.

注意:对于 DATA_CHANGED 事件,还需调用 lo_grid->register_edit_event( ) 注册编辑事件。


题目6 - BAdI 概念与使用 [记忆]

什么是 BAdI(Business Add-In)?请说明 BAdI 的基本使用流程,包括 GET BADI 和 CALL BADI 的作用。

📌查看答案
BAdI 定义
BAdI(Business Add-In)是 SAP 提供的基于面向对象的增强技术,利用接口实现可插拔的业务逻辑增强。

BAdI 的核心组件

  • BAdI 定义:包含一个全局接口(如 ZIF_BADI_EXAMPLE)
  • BAdI 实现:实现该接口的具体类(如 ZCL_BADI_IMPL)

使用流程

1
2
3
4
5
6
7
8
9
10
11
12
13
" 1. 获取 BAdI 实例(GET BADI)
" 系统根据筛选条件找到匹配的实现类
GET BADI lo_badi
FILTERS
country = 'CN'.

" 2. 调用 BAdI 方法(CALL BADI)
" 系统调用所有活跃实现类的对应方法
CALL BADI lo_badi->calculate_price
EXPORTING
iv_material = lv_matnr
CHANGING
cv_price = lv_price.

关键特点

  • 支持多实现(多个实现类可以同时活跃)
  • 支持筛选条件(Filter)区分不同场景
  • 基于接口,实现完全解耦
  • 使用事务 SE18(定义)和 SE19(实现)管理

题目7 - 全局类设计分析 [分析]

假设你要设计一个全局类 ZCL_ORDER_MANAGER,它需要:1) 支持多种订单类型的处理逻辑;2) 允许客户添加自定义处理逻辑;3) 提供 ALV 显示功能。请分析应该如何组织这个全局类及其相关接口,并说明设计理由。

📌查看答案
推荐设计方案
  1. 核心接口设计

    1
    2
    3
    4
    5
    6
    " 订单处理策略接口
    INTERFACE zif_order_processor PUBLIC.
    METHODS: process_order
    IMPORTING io_order TYPE REF TO zcl_order
    RETURNING VALUE(rv_success) TYPE abap_bool.
    ENDINTERFACE.
  2. 主类设计

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    CLASS zcl_order_manager DEFINITION
    PUBLIC CREATE PRIVATE. " 单例或工厂模式
    PUBLIC SECTION.
    INTERFACES: zif_order_processor.
    " 工厂方法
    CLASS-METHODS: get_instance
    RETURNING VALUE(ro_instance) TYPE REF TO zcl_order_manager.
    METHODS: display_orders,
    set_processor.
    PRIVATE SECTION.
    DATA: mo_processor TYPE REF TO zif_order_processor,
    mo_alv_grid TYPE REF TO cl_gui_alv_grid,
    mt_orders TYPE ztt_orders.
    ENDCLASS.

设计理由

  • 接口分离:使用 zif_order_processor 实现策略模式,支持多种订单处理逻辑的灵活切换
  • BAdI 增强:可通过 BAdI 暴露 zif_order_processor,让客户实现自定义逻辑
  • ALV 封装:将 ALV 显示封装在类内部,使用者无需了解 ALV 细节
  • CREATE PRIVATE:配合工厂方法,控制实例化过程
  • 组合优于继承:通过接口注入处理策略,而非通过继承子类化

题目8 - ALV Grid 交互式应用 [应用]

请编写一个完整的 ALV Grid 应用程序的关键代码片段:在屏幕上显示内表数据,双击某行时弹出该行的详细信息。要求使用全局类风格的事件处理。

📌查看答案
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
" ---- 数据声明 ----
TYPES: BEGIN OF ty_flight,
carrid TYPE sflight-carrid,
connid TYPE sflight-connid,
fldate TYPE sflight-fldate,
price TYPE sflight-price,
END OF ty_flight.
DATA: gt_flight TYPE STANDARD TABLE OF ty_flight,
go_grid TYPE REF TO cl_gui_alv_grid,
go_cont TYPE REF TO cl_gui_custom_container.

" ---- 事件处理类 ----
CLASS lcl_event_handler DEFINITION.
PUBLIC SECTION.
METHODS on_double_click
FOR EVENT double_click OF cl_gui_alv_grid
IMPORTING e_row e_column es_row_no.
ENDCLASS.

CLASS lcl_event_handler IMPLEMENTATION.
METHOD on_double_click.
DATA: ls_flight TYPE ty_flight.
READ TABLE gt_flight INTO ls_flight INDEX e_row-index.
IF sy-subrc = 0.
MESSAGE s000(oo) WITH '航班:' ls_flight-carrid
'价格:' ls_flight-price.
ENDIF.
ENDMETHOD.
ENDCLASS.

" ---- PBO Module ----
MODULE status_0100 OUTPUT.
IF go_grid IS NOT BOUND.
CREATE OBJECT go_cont
EXPORTING container_name = 'CC_ALV'.
CREATE OBJECT go_grid
EXPORTING i_parent = go_cont.

DATA(lo_handler) = NEW lcl_event_handler( ).
SET HANDLER lo_handler->on_double_click FOR go_grid.

SELECT carrid connid fldate price
FROM sflight INTO TABLE gt_flight
UP TO 50 ROWS.

DATA: lt_fcat TYPE lvc_t_fcat.
" 自动生成字段目录
CALL FUNCTION 'LVC_FIELDCATALOG_MERGE'
EXPORTING
i_structure_name = 'SFLIGHT'
CHANGING
ct_fieldcat = lt_fcat.

go_grid->set_table_for_first_display(
CHANGING
it_outtab = gt_flight
it_fieldcatalog = lt_fcat ).
ENDIF.
ENDMODULE.

关键点:事件处理程序必须在 ALV 创建之后注册;e_row-index 用于获取被双击的行号。


📌模式总结 (点击展开)
关键词 答案
全局类 SE24 创建,存储在 Repository,所有程序可用
局部类 程序内定义,仅本程序可见
SE24 标签页 Properties / Interfaces / Friends / Attributes / Methods / Events / Types
瞬时性 PUBLIC / PROTECTED / PRIVATE / ABSTRACT
全局接口 SE24 中 Interface 类型,以 ZIF_ 开头
ALV Grid CL_GUI_ALV_GRID + 容器 + 字段目录 + 布局
ALV 事件 DATA_CHANGED / DOUBLE_CLICK / TOOLBAR / USER_COMMAND
BAdI GET BADI + CALL BADI,基于接口的增强
SE18/SE19 分别用于 BAdI 定义和实现
策略模式 通过接口注入不同实现

事件 练习

#practice #events #oo-events #abap-objects

📌核心模式 (点击展开)
关键词 答案
实例事件定义 EVENTS evt_name [EXPORTING ...]
静态事件定义 CLASS-EVENTS evt_name [EXPORTING ...]
触发事件 RAISE EVENT evt_name
注册处理程序 SET HANDLER handler->on_event FOR instance
SENDER 参数 自动传递的发送者引用

题目1 - 事件定义关键字 [记忆]

在 ABAP Objects 中,定义实例事件和静态事件分别使用哪两个关键字?

📌查看答案
  • 实例事件:使用 EVENTS 关键字定义
  • 静态事件:使用 CLASS-EVENTS 关键字定义

语法示例:

1
2
3
4
5
6
7
CLASS lcl_example DEFINITION.
PUBLIC SECTION.
EVENTS: data_changed " 实例事件
EXPORTING VALUE(e_data) TYPE string.
CLASS-EVENTS: status_update " 静态事件
EXPORTING VALUE(e_status) TYPE char1.
ENDCLASS.

实例事件绑定到具体对象实例,静态事件属于类本身,不依赖实例即可触发和监听。


题目2 - RAISE EVENT 语法 [记忆]

如何在类的方法中触发(raise)一个事件?请写出基本语法,并说明实例事件和静态事件的触发有何区别。

📌查看答案
使用 RAISE EVENT 语句触发事件:
1
2
3
4
RAISE EVENT evt_name
EXPORTING
e_param1 = value1
e_param2 = value2.

区别说明:

  • 实例事件:在实例方法中使用 RAISE EVENT 触发。也可在静态方法中触发同一类的实例事件(但实际不推荐,因为静态方法没有明确的实例上下文)。
  • 静态事件:可在实例方法或静态方法中使用 RAISE EVENT 触发。

注意:触发事件时,EXPORTING 参数必须与事件定义中的参数匹配。


题目3 - 事件处理程序注册 [记忆]

写出 SET HANDLER 的三种注册形式,并解释每种形式的适用场景。

📌查看答案
三种 SET HANDLER 注册形式:
  1. 为特定实例注册

    1
    SET HANDLER handler->on_event FOR obj.

    只监听对象 obj 触发的事件。

  2. 为所有实例注册

    1
    SET HANDLER handler->on_event FOR ALL INSTANCES.

    监听该类所有现有和未来创建的实例触发的事件。

  3. 为静态事件注册

    1
    SET HANDLER handler->on_event.

    不需要指定 FOR,因为静态事件不绑定到实例。

注意:如果不再需要监听,使用 SET HANDLER handler->on_event FOR obj ACTIVATION space. 取消注册。


题目4 - 事件处理方法定义 [记忆]

事件处理方法(handler method)的定义语法是什么?它与普通方法在参数声明上有什么特殊限制?

📌查看答案
事件处理方法使用 FOR EVENT 语法定义:
1
2
3
METHODS on_data_changed
FOR EVENT data_changed OF lcl_sender
IMPORTING sender e_data.

特殊限制:

  • 处理方法的参数只能 IMPORTING(不能有 EXPORTING、CHANGING、RETURNING)
  • 参数列表必须与事件定义的 EXPORTING 参数匹配
  • SENDER 是一个隐式可用的参数,代表触发事件的对象引用,类型为触发事件的类引用
  • 即使事件没有定义参数,处理方法仍可声明 IMPORTING sender

题目5 - SENDER 参数的作用 [记忆]

在事件处理方法中,SENDER 参数的作用是什么?它是如何传递的?

📌查看答案
SENDER 参数的作用
  • SENDER 是事件处理方法中的一个隐式可用参数
  • 它持有触发事件的对象实例的引用
  • 类型为触发事件所在类的对象引用
  • 通过 SENDER,处理方法可以访问触发者的公共属性和方法

传递机制

  • SENDER 不需要在事件的 EXPORTING 列表中显式声明
  • 它由 ABAP 运行时环境自动传递
  • 处理方法可以自由选择是否在 IMPORTING 中声明使用 SENDER
1
2
3
METHODS on_event
FOR EVENT data_changed OF lcl_source
IMPORTING sender e_data. " sender 可选声明

题目6 - 事件参数传递方向分析 [分析]

为什么事件处理方法(handler method)只能有 IMPORTING 参数,而不能有 EXPORTING 或 RETURNING?请从事件机制的通信模型角度分析原因。

📌查看答案
这与事件的 一对多广播通信模型 有关:
  1. 一对多关系:一个事件可以有多个处理程序(handler)。如果允许处理方法返回值或输出参数,系统无法确定应该将哪个处理程序的返回值传递回触发者。

  2. 解耦设计:事件机制的核心是触发者不需要知道谁在监听。如果处理方法能向触发者传回数据,就打破了这种单向通信的解耦。

  3. 异步语义:触发者发出事件后继续执行,不等待处理结果。如果需要双向通信,应使用直接方法调用而非事件。

如果确实需要在处理方法中改变数据,可以通过引用传递已有对象(在事件参数中传递对象引用),处理方法修改该对象的状态。


题目7 - 完整事件流程编写 [应用]

请根据以下需求编写完整的 ABAP 代码:

  • lcl_timer 有一个静态事件 timeout,导出参数 e_seconds TYPE i
  • lcl_display 有处理方法 on_timeout,接收 e_seconds 并写入 WRITE 输出
  • 在启动程序中注册处理程序并触发事件
📌查看答案
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
31
32
CLASS lcl_timer DEFINITION.
PUBLIC SECTION.
CLASS-EVENTS timeout
EXPORTING VALUE(e_seconds) TYPE i.
CLASS-METHODS: start
IMPORTING iv_seconds TYPE i.
ENDCLASS.

CLASS lcl_timer IMPLEMENTATION.
METHOD start.
RAISE EVENT timeout EXPORTING e_seconds = iv_seconds.
ENDMETHOD.
ENDCLASS.

CLASS lcl_display DEFINITION.
PUBLIC SECTION.
METHODS on_timeout
FOR EVENT timeout OF lcl_timer
IMPORTING e_seconds.
ENDCLASS.

CLASS lcl_display IMPLEMENTATION.
METHOD on_timeout.
WRITE: / '超时!', e_seconds, '秒已过'.
ENDMETHOD.
ENDCLASS.

" 启动程序
START-OF-SELECTION.
DATA(lo_display) = NEW lcl_display( ).
SET HANDLER lo_display->on_timeout. " 静态事件无需 FOR
lcl_timer=>start( iv_seconds = 30 ).

题目8 - 事件与实例生命周期 [应用]

假设你有一个类 lcl_document,每当文档状态改变时触发事件 status_changed。多个监听者(logger、notifier)需要监听此事件。当文档对象被删除后,注册的处理程序会发生什么?如何安全地管理事件注册的生命周期?

📌查看答案
1
2
3
4
5
6
7
" 事件定义
CLASS lcl_document DEFINITION.
PUBLIC SECTION.
EVENTS status_changed
EXPORTING VALUE(e_new_status) TYPE char10.
METHODS: change_status.
ENDCLASS.

生命周期管理要点

  1. 垃圾回收机制:当文档对象被删除(没有引用指向它)后,ABAP 垃圾回收器会自动清除与该实例相关的事件注册。

  2. 显式取消注册:最佳实践是在不再需要监听时显式取消注册:

    1
    2
    SET HANDLER lo_logger->on_status_changed
    FOR lo_document ACTIVATION space.
  3. 内存泄漏风险:如果使用 FOR ALL INSTANCES 注册且处理程序对象生命周期很长,可能导致不必要的监听。应确保在适当时机取消注册。

  4. 安全模式:建议在处理程序类中提供 register( )unregister( ) 方法封装 SET HANDLER 逻辑,在 cleanup 方法中统一取消所有注册。


📌模式总结 (点击展开)
关键词 答案
实例事件 EVENTS 关键字定义,绑定到对象实例
静态事件 CLASS-EVENTS 关键字定义,属于类级别
触发事件 RAISE EVENT evt_name EXPORTING ...
注册-指定实例 SET HANDLER h FOR obj
注册-所有实例 SET HANDLER h FOR ALL INSTANCES
注册-静态事件 SET HANDLER h(无 FOR 子句)
取消注册 SET HANDLER ... ACTIVATION space
处理方法定义 FOR EVENT evt OF cls IMPORTING ...
处理方法限制 只能有 IMPORTING 参数
SENDER 隐式传递的触发者对象引用
一对多模型 一个事件可有多个处理程序
生命周期 对象删除后注册自动失效
0%