Mark的技术博客

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

吴让宇(Mark)

吴让宇(Mark)

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

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

SAP Fiori ABAP CDS OData Dify Docker AI LLM

最新文章

ABAP 填充内表

📊 内表填充概述

填充方式

在内部表中,填充包括选择、插入和追加等功能。本章重点介绍INSERT和APPEND语句。

填充策略



graph TD
    A[内表填充] --> B[单行操作]
    A --> C[批量操作]
    
    B --> B1[INSERT]
    B --> B2[APPEND]
    B --> B3[MODIFY]
    
    C --> C1[批量插入]
    C --> C2[循环填充]
    C --> C3[SELECT结果]

🔧 INSERT语句

功能

INSERT语句用于将单个行或一组行插入内部表。

语法

1
INSERT <work_area_itab> INTO <internal_tab> INDEX <index_num>.

语法详解

参数 说明 示例
工作区,提供要插入的数据 wa_customer
目标内部表 lt_customers
INDEX 插入位置(可选) INDEX 2

执行逻辑

  1. 插入位置:当使用INDEX子句时,插入行之后的行的索引号增加1
  2. 边界情况:如果internal_tab包含 - 1行,新行添加在表的末尾
  3. 返回码:当SAP系统成功地将行添加到内部表时,SY-SUBRC变量设置为0

例子

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
REPORT  zcuslist1.

DATA:
BEGIN OF itable1 OCCURS 4,
f1 LIKE sy-index,
END OF itable1.

* 填充初始数据
DO 4 TIMES.
itable1-f1 = sy-index.
APPEND itable1.
ENDDO.

* 在索引2位置插入行
itable1-f1 = -96.
INSERT itable1 INDEX 2.

* 输出结果
LOOP AT itable1.
WRITE / itable1-f1.
ENDLOOP.

* 在符合条件的行之前插入
LOOP AT itable1 WHERE f1 >= 3.
itable1-f1 = -78.
INSERT itable1.
ENDLOOP.
SKIP.

* 再次输出
LOOP AT itable1.
WRITE / itable1-f1.
ENDLOOP.

输出示例

1
2
3
4
5
6
7
8
9
10
11
12
13
1
96-
2
3
4

1
96-
2
78-
3
78-
4

INSERT注意事项

  1. 性能开销:执行每个insert语句后,系统会重新编制所插入的行下面的所有行
  2. 大型表插入:当在大型内部表的顶部附近插入行时,会引入开销
  3. 推荐做法:准备另一个包含要插入的行的表,并使用批量插入
  4. 循环内插入:在循环内插入不会立即影响内部表,而是在下一个循环传递中生效

🔧 APPEND语句

功能

APPEND语句用于向现有内部表添加单个行或行。此语句从工作区复制单个行,并将其插入内部表中的最后一个现有行之后。

语法

1
APPEND <record_for_itab> TO <internal_tab>.

语法详解

参数 说明 示例
要添加的记录 wa_customer 或 INITIAL LINE
目标内部表 lt_customers

工作区来源

来源 说明
标题行 使用内表的标题行作为源
其他工作区 使用与内表行具有相同结构的字段字符串
INITIAL LINE 添加一个空行,包含初始值

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
REPORT  zcuslist1.

DATA:
BEGIN OF linv OCCURS 0,
Name(20) TYPE c,
ID_Number TYPE i,
END OF linv.
DATA table1 LIKE TABLE OF linv.

* 填充数据
linv-Name = 'Melissa'.
linv-ID_Number = 105467.
APPEND linv TO table1.

* 输出结果
LOOP AT table1 INTO linv.
WRITE: / linv-name, linv-ID_Number.
ENDLOOP.

输出示例

1
Melissa     105467

⚠️ 键和排序注意事项

标准表和排序表

  • 非唯一键:使用非唯一键将行附加到标准和排序表中,无论表中是否存在具有相同键的行
  • 重复条目:可能发生重复条目

排序表的特殊情况



graph LR
    A[排序表] --> B[唯一键检查]
    A --> C[排序顺序检查]
    
    B --> B1[重复条目→运行时错误]
    C --> C1[违反顺序→运行时错误]

避免运行时错误

1
2
3
4
5
6
* 检查唯一性后插入
IF line_exists( table1 WITH TABLE KEY Name = 'Test' ).
WRITE: / '记录已存在'.
ELSE.
APPEND linv TO table1.
ENDIF.

💡 填充最佳实践

1. 批量操作

1
2
3
4
5
6
7
8
9
* 使用批量INSERT提高性能
DATA: lt_new_data TYPE TABLE OF ty_customer,
wa_new TYPE ty_customer.

* 填充临时表
APPEND wa_new TO lt_new_data.

* 批量插入
INSERT LINES OF lt_new_data INTO TABLE lt_customers.

2. 条件填充

1
2
3
4
* 只在条件满足时填充
IF lv_flag = abap_true.
APPEND wa_customer TO lt_customers.
ENDIF.

3. 错误处理

1
2
3
4
5
6
7
8
9
10
11
12
13
* 检查插入结果
INSERT wa_customer INTO TABLE lt_customers.

IF sy-subrc <> 0.
CASE sy-subrc.
WHEN 1.
WRITE: / '插入失败:重复键'.
WHEN 2.
WRITE: / '插入失败:违反排序'.
WHEN OTHERS.
WRITE: / '插入失败:未知错误'.
ENDCASE.
ENDIF.

4. 性能优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
* 在循环外填充工作区
DO 1000 TIMES.
wa_customer-id = sy-index.
wa_customer-name = 'Customer' && sy-index.
APPEND wa_customer TO lt_customers.
ENDDO.

* 推荐:先填充临时表,再批量插入
DATA: lt_temp TYPE TABLE OF ty_customer.

DO 1000 TIMES.
wa_customer-id = sy-index.
APPEND wa_customer TO lt_temp.
ENDDO.

INSERT LINES OF lt_temp INTO TABLE lt_customers.

📊 INSERT vs APPEND

特性 INSERT APPEND
插入位置 指定索引位置 末尾
性能 较慢(需要重新索引) 较快
灵活性 高(可指定位置) 低(只能末尾)
适用场景 特定位置插入 简单追加
系统开销 大(移动后续行) 小(直接追加)

ABAP 基本语法

ℹ️学习要点
ABAP是一种第四代编程语言,专门为SAP系统设计。了解基本语法是掌握ABAP开发的第一步。

🔤 语句结构

ABAP源程序由注释和ABAP语句组成。ABAP中的每个语句都以关键字开头,以句点(.)结尾,ABAP不区分大小写。

程序开始

程序中的第一个非注释行以单词REPORT开头。报告将始终是创建的任何可执行程序的第一行。

1
2
REPORT [Program_Name].
[Statements…].

示例:

1
REPORT Z_Test123_01.
💡语句要点
  • ABAP编辑器将所有文本转换为大写,除了由单引号包围的文本字符串
  • 与一些较旧的编程语言不同,ABAP不关心语句在一行上开始的位置
  • ABAP对语句的布局没有限制,可以在单个行上放置多个语句,或者单个语句可以跨多个行扩展

基本语句示例

1
2
REPORT Z_Test123_01.
WRITE 'This is ABAP Tutorial'.

🔗 冒号表示法

如果每个语句的开头是相同的,则连续语句可以链接在一起。这时使用冒号(:)运算符和逗号。

常规写法

1
2
3
WRITE 'Hello'.
WRITE 'ABAP'.
WRITE 'World'.

冒号表示法

1
2
3
WRITE:  'Hello',
'ABAP',
'World'.
💥布局灵活性
像任何其他ABAP语句,布局没有关系。以下是同样正确的声明:
1
WRITE: 'Hello', 'ABAP', 'World'.

💬 注释

全行注释

通过在行的第一个位置放置星号(*)来指示全行注释。

1
* This is the comment line

部分行注释

通过在语句之后输入双引号(")来表示部分行注释。

1
WRITE 'Hello'. "Here is the partial comment
⚠️注意事项
注释代码未由ABAP编辑器大写。

🚫 抑制空白

NO-ZERO命令在DATA语句之后。它抑制包含空格的数字字段的所有前导零。

示例代码

1
2
3
4
REPORT Z_Test123_01.
DATA: W_NUR(10) TYPE N.
MOVE 50 TO W_NUR.
WRITE W_NUR NO-ZERO.

输出结果

1
50
📌对比
没有NO-ZERO命令,输出为:0000000050

📄 空白行

SKIP命令有助于在页面上插入空行。

基本用法

1
2
3
WRITE 'This is the 1st line'.
SKIP.
WRITE 'This is the 2nd line'.

输出:

1
2
3
This is the 1st line

This is the 2nd line

高级用法

1
2
SKIP number_of_lines.  * 插入多个空白行
SKIP TO LINE line_number. * 定位到指定行
💡使用场景
SKIP命令还可以将光标定位在页面上所需的行上。通常,在此命令后发出WRITE语句,将输出放在所需的行上。

➖ 插入线

ULINE命令自动在输出插入一条水平线。

示例

1
2
WRITE 'This is Underlined'.
ULINE.

输出:

1
2
This is Underlined
-------------------

📨 消息系统

MESSAGE命令显示由程序开始处REPORT语句中指定的消息ID定义的消息。消息ID是一个2字符代码,用于定义在使用MESSAGE命令时程序将访问的1000个消息的集合。

消息类型

信息 类型 后果
E Error 消息出现,应用程序在当前点暂停。如果程序在后台模式下运行,则作业将被取消,并且消息将记录在作业日志中。
W Warning 将出现该消息,用户必须按Enter键才能继续应用程序。在后台模式下,消息将记录在作业日志中。
I Information 将打开一个弹出窗口,其中包含消息文本,用户必须按Enter键才能继续。在后台模式下,消息将记录在作业日志中。
A Abend 此消息类将取消用户当前正在使用的事务。
S Success 这会在屏幕底部提供一条信息性消息。显示的信息是正性的,它只是为用户反馈。该消息不以任何方式阻碍程序。
X Abort 此消息中止程序并生成ABAP短转储。

示例

1
MESSAGE E011.

输出:

1
EAB011 This report does not support sub-number summarization.
📌消息使用建议
  • 错误消息(E):通常用于阻止用户做不应该做的事情
  • 警告消息(W):通常用于提醒用户他们的行动的后果
  • 信息消息(I):给用户有用的信息

🔄 ABAP基本语句流程图



graph TD
    A[程序开始] --> B[REPORT语句]
    B --> C{是否有语句?}
    C -->|是| D[执行语句]
    C -->|否| E[程序结束]
    D --> F{是否需要输出?}
    F -->|是| G[使用WRITE输出]
    F -->|否| C
    G --> H{是否需要格式化?}
    H -->|是| I[使用SKIP/ULINE格式化]
    H -->|否| C

💡 最佳实践

  1. 代码格式化:使用适当的缩进来提高代码可读性
  2. 注释规范:关键逻辑处添加注释,说明代码用途
  3. 消息管理:合理使用不同类型的消息,提供良好的用户体验
  4. 错误处理:使用E类型消息阻止不合法操作
  5. 性能考虑:合理使用冒号表示法减少代码冗余

ABAP 字符串

ℹ️概述
在ABAP编程中广泛使用的字符串是一系列字符。我们使用数据类型C变量保存字母数字字符,最少为1个字符,最多为65,535个字符。默认情况下,这些向左对齐。

📝 创建字符串

基本声明

以下声明和初始化创建一个由”Hello”字组成的字符串。字符串的大小正好是单词”Hello”中的字符数。

1
Data my_Char(5) VALUE 'Hello'.

示例程序

1
2
3
REPORT YT_SEP_15.  
DATA my_Char(5) VALUE 'Hello'.
Write my_Char.

输出

1
Hello

📏 字符串长度

为了找到字符串的长度,我们可以使用 STRLEN语句。STRLEN()函数返回字符串中包含的字符数。

示例

1
2
3
4
5
6
7
REPORT YT_SEP_15.
DATA:
title_1(10) VALUE 'Tutorials',
length_1 TYPE I.

length_1 = STRLEN( title_1 ).
Write: / 'The Length of the Title is:', length_1.

输出

1
The Length of the Title is: 9

🔧 字符串操作语句

ABAP支持处理字符串的各种语句。

编号 语句 用途
1 CONCATENATE 连接两个字符串形成第三个字符串
2 CONDENSE 删除空格字符
3 STRLEN 用于查找字段的长度
4 REPLACE 用于以字符进行替换
5 SEARCH 在字符串中运行搜索
6 SHIFT 用于向左或向右移动字符串的内容
7 SPLIT 用于将字段的内容拆分为两个或多个字段

📝 字符串操作示例

1. CONCATENATE - 字符串连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
REPORT YT_SEP_15.
DATA:
title_1(10) VALUE 'Tutorials',
title_2(10) VALUE 'Point',
sep TYPE C VALUE ' ',
dest1(30),
dest2(30).

" 直接连接
CONCATENATE title_1 title_2 INTO dest1.
Write: / 'Concatenation:', dest1.

" 带分隔符的连接
CONCATENATE title_1 title_2 INTO dest2 SEPARATED BY sep.
Write: / 'Concatenation with Space:', dest2.

输出

1
2
Concatenation: TutorialsPoint
Concatenation with Space: Tutorials Point

2. CONDENSE - 压缩空格

1
2
3
4
5
6
7
8
9
DATA: spaced_title(30) VALUE 'Tutorials  Point  Limited'.

" 压缩空格(保留一个)
CONDENSE spaced_title.
Write: / 'Condense with Gaps:', spaced_title.

" 删除所有空格
CONDENSE spaced_title NO-GAPS.
Write: / 'Condense with No Gaps:', spaced_title.

输出

1
2
Condense with Gaps: Tutorials Point Limited
Condense with No Gaps: TutorialsPointLimited
📌CONDENSE说明
  • CONDENSE语句删除字段之间的空格,但只留下1个字符的空格
  • ‘NO-GAPS’是CONDENSE语句的可选添加,删除所有空格

🔍 其他字符串操作

3. REPLACE - 字符替换

1
2
3
4
DATA: text TYPE C VALUE 'Hello World'.

REPLACE 'World' WITH 'ABAP' INTO text.
Write: / 'After REPLACE:', text.

输出

1
After REPLACE: Hello ABAP

4. SEARCH - 字符串搜索

1
2
3
4
5
6
7
8
9
10
DATA: 
text TYPE C VALUE 'This is a test string',
offset TYPE I.

offset = SEARCH text FOR 'test'.
IF offset > 0.
Write: / 'Found at position:', offset.
ELSE.
Write: / 'Not found'.
ENDIF.

输出

1
Found at position: 11

5. SHIFT - 字符串移动

1
2
3
4
5
6
7
8
9
10
DATA: text TYPE C VALUE 'ABAP'.

" 向左移动2位
SHIFT text BY 2 PLACES TO THE LEFT.
Write: / 'After left shift:', text. " 输出: AP

" 重置并向右移动
text = 'ABAP'.
SHIFT text BY 2 PLACES TO THE RIGHT.
Write: / 'After right shift:', text. " 输出: ABAP

6. SPLIT - 字符串分割

1
2
3
4
5
6
7
8
DATA: 
full_name TYPE C VALUE 'John Doe',
first_name(10),
last_name(10).

SPLIT full_name AT SPACE INTO first_name last_name.
Write: / 'First Name:', first_name.
Write: / 'Last Name:', last_name.

输出

1
2
First Name: John
Last Name: Doe

🎯 高级字符串操作

多字符串连接

1
2
3
4
5
6
7
8
9
DATA: 
part1 TYPE C VALUE 'Hello',
part2 TYPE C VALUE ' ',
part3 TYPE C VALUE 'World',
result TYPE C VALUE 'CONCATENATE part1 part2 part3 INTO result'.

" 使用WITH选项
CONCATENATE part1 part2 part3 INTO result SEPARATED BY space.
Write: / 'Result:', result.

动态字符串处理

1
2
3
4
5
6
7
8
DATA: 
text TYPE C VALUE 'Initial Text',
search_char TYPE C VALUE 'x',
replace_char TYPE C VALUE 'y'.

" 动态替换
REPLACE ALL OCCURRENCES OF search_char WITH replace_char INTO text.
Write: / 'After replace all:', text.

⚠️ 注意事项

1. 字符串长度限制

1
2
3
4
5
" ❌ 错误:超过最大长度
DATA: too_long TYPE C VALUE 'A very long string...'.

" ✅ 正确:声明足够长度
DATA: proper_length(65535) VALUE 'A very long string...'.

2. 字符串对齐

1
2
3
4
5
" C类型向左对齐
DATA: left_aligned(10) VALUE 'ABAP'.

" 查看填充情况
Write: / 'Left aligned:', left_aligned.

3. 特殊字符处理

1
2
3
4
5
6
7
" 处理特殊字符
DATA:
text TYPE C VALUE 'Line 1\nLine 2',
escaped_text TYPE C VALUE 'Line 1\\nLine 2'.

Write: / 'Original:', text.
Write: / 'Escaped:', escaped_text.

💡 实际应用场景

1. 文本格式化

1
2
3
4
5
6
7
8
9
10
" 格式化输出
DATA:
name TYPE C VALUE 'John Doe',
age TYPE I VALUE 30,
output TYPE C VALUE 100.

FORMAT COLOR COL_POSITIVE.
WRITE: / 'Name:', name.
FORMAT COLOR COL_NEGATIVE.
WRITE: / 'Age:', age.

2. 数据验证

1
2
3
4
5
6
7
8
" 验证输入
DATA: input TYPE C VALUE '12345'.

IF STRLEN( input ) = 5 AND input CO '0123456789'.
WRITE: / 'Valid input'.
ELSE.
WRITE: / 'Invalid input'.
ENDIF.

3. 文本处理

1
2
3
4
5
6
7
8
9
10
" 处理多行文本
DATA:
text TYPE C VALUE 'First line\nSecond line\nThird line',
line TYPE C(50).

SPLIT text AT '\n' INTO TABLE DATA(lt_lines).

LOOP AT lt_lines INTO line.
WRITE: / line.
ENDLOOP.

📊 字符串操作性能对比

操作 时间复杂度 适用场景
CONCATENATE O(n+m) 字符串连接
STRLEN O(1) 获取长度
SEARCH O(n) 查找子串
REPLACE O(n) 字符替换
SHIFT O(n) 字符移动
SPLIT O(n) 字符分割

💡 最佳实践

1. 字符串设计原则

  • 使用合适的长度声明
  • 考虑字符对齐需求
  • 避免硬编码字符串

2. 性能优化

  • 避免频繁的字符串操作
  • 使用合适的字符串函数
  • 注意大字符串的内存使用

3. 代码质量

  • 使用有意义的变量名
  • 添加必要的注释
  • 保持字符串操作的清晰

4. 错误处理

  • 检查字符串长度
  • 验证字符串内容
  • 处理可能的异常情况

ABAP 多态性

📖 简介

多态性(Polymorphism)字面意思是”多种形式”。从面向对象的角度来看,多态性与继承一起工作,使得继承树中的各种类型可以互换使用。也就是说,当存在类的层次结构并且它们通过继承相关时,发生多态性。

ABAP多态意味着对方法的调用将导致根据调用方法的对象的类型执行不同的方法。

🔄 多态性原理

多态性的核心概念:

  • 继承关系:类之间通过继承形成层次结构
  • 方法重写:子类可以重写父类的抽象方法
  • 动态绑定:运行时根据对象的实际类型绑定相应的方法实现
  • 类型转换:支持隐式类型转换,使代码更加灵活

🔗 关键特性



graph LR
    A[抽象基类] --> B[子类1]
    A --> C[子类2]
    B --> D[方法重写]
    C --> D
    D --> E[动态方法调用]

💡 实现原理

当方法被调用时,ABAP运行时环境执行以下步骤:

  1. 参数传递:检查方法签名和参数类型
  2. 隐式类型转换:将子类对象隐式转换为父类引用
  3. 动态绑定:根据对象的实际类型绑定相应的方法实现
  4. 方法执行:执行对应类型的特定实现

注意 - ABAP运行时环境在导入参数分配期间执行隐式精简转换。此功能有助于方法的通用实现。与对象引用变量相关联的动态类型信息允许ABAP运行时环境动态地将方法调用与由对象引用变量指向的对象中定义的实现绑定。

📝 示例程序

以下程序包含一个抽象类 class_prgm,2个子类(class_proceduralclass_OO)和一个测试驱动程序类 class_type_approach

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
REPORT zpolymorphism1.

CLASS class_prgm DEFINITION ABSTRACT.
PUBLIC SECTION.
METHODS:
prgm_type ABSTRACT,
approach1 ABSTRACT.
ENDCLASS.

CLASS class_procedural DEFINITION INHERITING FROM class_prgm.
PUBLIC SECTION.
METHODS:
prgm_type REDEFINITION,
approach1 REDEFINITION.
ENDCLASS.

CLASS class_procedural IMPLEMENTATION.
METHOD prgm_type.
WRITE: 'Procedural programming'.
ENDMETHOD.
METHOD approach1.
WRITE: 'top-down approach'.
ENDMETHOD.
ENDCLASS.

CLASS class_oo DEFINITION INHERITING FROM class_prgm.
PUBLIC SECTION.
METHODS:
prgm_type REDEFINITION,
approach1 REDEFINITION.
ENDCLASS.

CLASS class_oo IMPLEMENTATION.
METHOD prgm_type.
WRITE: 'Object oriented programming'.
ENDMETHOD.
METHOD approach1.
WRITE: 'bottom-up approach'.
ENDMETHOD.
ENDCLASS.

CLASS class_type_approach DEFINITION.
PUBLIC SECTION.
CLASS-METHODS: start IMPORTING class1_prgm TYPE REF TO class_prgm.
ENDCLASS.

CLASS class_type_approach IMPLEMENTATION.
METHOD start.
CALL METHOD class1_prgm->prgm_type.
WRITE: 'follows'.
CALL METHOD class1_prgm->approach1.
ENDMETHOD.
ENDCLASS.

START-OF-SELECTION.
DATA:
class_1 TYPE REF TO class_procedural,
class_2 TYPE REF TO class_oo.
CREATE OBJECT class_1.
CREATE OBJECT class_2.
CALL METHOD class_type_approach=>start EXPORTING class1_prgm = class_1.
NEW-LINE.
CALL METHOD class_type_approach=>start EXPORTING class1_prgm = class_2.

📊 输出结果

1
2
Procedural programming follows top-down approach
Object oriented programming follows bottom-up approach

🎯 代码解析

类结构分析

类名 类型 作用
class_prgm 抽象基类 定义抽象方法
class_procedural 具体子类 实现过程化编程
class_oo 具体子类 实现面向对象编程
class_type_approach 驱动类 测试多态性

关键代码说明

1
2
3
4
CLASS class_type_approach DEFINITION.
PUBLIC SECTION.
CLASS-METHODS: start IMPORTING class1_prgm TYPE REF TO class_prgm.
ENDCLASS.
  • start 方法接收一个 class_prgm 类型的参数
  • 虽然声明为抽象类型,但在运行时接收具体子类对象
  • 调用方法时会根据实际对象类型执行相应实现
1
2
3
4
5
6
7
8
START-OF-SELECTION.
DATA:
class_1 TYPE REF TO class_procedural,
class_2 TYPE REF TO class_oo.
CREATE OBJECT class_1.
CREATE OBJECT class_2.
CALL METHOD class_type_approach=>start EXPORTING class1_prgm = class_1.
CALL METHOD class_type_approach=>start EXPORTING class1_prgm = class_2.
  1. 创建两个不同类型的对象
  2. 使用相同的方法调用,但根据对象类型产生不同输出
  3. 展示了多态性的本质:同一接口,不同实现

🚀 应用场景

1. 框架开发

  • 创建可扩展的框架结构
  • 允许用户自定义特定行为

2. 设计模式实现

  • 策略模式:不同的算法实现
  • 工厂模式:创建不同类型对象
  • 观察者模式:不同的事件处理

3. 业务逻辑扩展

  • 新业务功能可通过继承轻松扩展
  • 保持接口一致性

ABAP 子程序

🔄 子程序概述

定义

子程序是一个可重用的代码段,是程序中的模块化单元,其中函数以源代码的形式封装。

子程序的作用

将程序的一部分封装到子程序中,以:

  • 更好地概述主程序
  • 多次使用相应的语句序列
  • 提高代码的可维护性

可重用性示例



graph TD
    A[主程序] --> B[代码块1]
    A --> C[代码块2]
    A --> D[代码块3]
    
    B --> E[相同ABAP语句]
    C --> E
    D --> E
    
    E --> F[子程序封装]
    F --> G[多次调用]

📝 子程序语法

基本语法

1
2
3
FORM <subroutine_name>.
<statements>
ENDFORM.

调用语法

1
PERFORM <subroutine_name>.

执行流程

  1. 使用 PERFORM 语句调用子程序
  2. 控制跳转到子程序中的第一可执行语句
  3. 当遇到 ENDFORM 时,控制跳回到 PERFORM 语句之后的语句

🛠️ 创建子程序

步骤1:启动事务

使用事务 SE80,打开现有程序(如’ZSUBTEST’),右键单击程序。

步骤2:选择创建类型

选择”创建”,然后选择”子例程”。在字段中写入子程序名称(如”Sub_Display”),点击继续按钮。

步骤3:编写子程序代码

FORMENDFORM 语句块中编写代码。

步骤4:调用子程序

在主程序中使用 PERFORM 语句调用子程序。

💡 完整示例

主程序代码

1
2
3
4
5
6
7
8
9
REPORT ZSUBTEST.

* 调用子程序
PERFORM Sub_Display.

FORM Sub_Display.
WRITE: 'This is Subroutine'.
WRITE: / 'Subroutine created successfully'.
ENDFORM. " Sub_Display

输出示例

1
2
3
Subroutine Test:
This is Subroutine
Subroutine created successfully

🔧 带参数的子程序

参数传递语法

1
2
3
4
FORM <subroutine_name> USING <param1> <param2>
CHANGING <param3> <param4>.
<statements>
ENDFORM.

参数类型

参数类型 关键字 用途 方向
输入参数 USING 传入数据 IN
输出参数 CHANGING 传出数据 IN/OUT
表参数 TABLES 传入表数据 IN/OUT

带参数的示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
REPORT ZPARAMETER_EXAMPLE.

DATA: lv_name TYPE string VALUE '张三',
lv_age TYPE i VALUE 25,
lv_result TYPE string.

* 调用带参数的子程序
PERFORM display_info USING lv_name lv_age
CHANGING lv_result.

WRITE: / lv_result.

FORM display_info USING iv_name TYPE string
iv_age TYPE i
CHANGING cv_result TYPE string.
DATA: lv_info TYPE string.

CONCATENATE '姓名:' iv_name ', 年龄:' iv_age INTO lv_info.
cv_result = lv_info.

WRITE: / lv_info.
ENDFORM. " display_info

🎯 子程序的优势

1. 代码重用

1
2
3
4
5
6
7
8
9
* 多次调用同一子程序
PERFORM calculate_tax USING 1000 CHANGING lv_tax1.
PERFORM calculate_tax USING 2000 CHANGING lv_tax2.
PERFORM calculate_tax USING 3000 CHANGING lv_tax3.

FORM calculate_tax USING iv_amount TYPE p
CHANGING cv_tax TYPE p.
cv_tax = iv_amount * 0.10.
ENDFORM.

2. 模块化设计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
REPORT z_modular_design.

START-OF-SELECTION.
* 初始化数据
PERFORM initialize_data.

* 处理业务逻辑
PERFORM process_orders.

* 输出结果
PERFORM display_results.

FORM initialize_data.
" 初始化代码
ENDFORM.

FORM process_orders.
" 处理订单代码
ENDFORM.

FORM display_results.
" 显示结果代码
ENDFORM.

3. 简化维护

1
2
3
4
5
6
7
8
9
10
11
* 集中管理业务逻辑
FORM validate_order USING iv_order_id TYPE vbeln.
" 验证订单逻辑
IF iv_order_id IS INITIAL.
MESSAGE '订单ID不能为空' TYPE 'E'.
ENDIF.
ENDFORM.

* 在多处调用
PERFORM validate_order USING '80000001'.
PERFORM validate_order USING '80000002'.

⚠️ 注意事项

1. 命名规范

  • 使用有意义的子程序名称
  • 遵循命名约定
  • 名称清晰表达功能

2. 参数设计

  • 明确参数的输入输出方向
  • 避免过多的参数
  • 使用合适的数据类型

3. 错误处理

  • 在子程序内处理错误
  • 提供适当的错误信息
  • 考虑返回值

ABAP 字典

📊 数据库语言分类

SQL的两个组成部分

分类 功能 示例命令
DML (数据操作语言) 查询和更新数据 SELECT, INSERT, UPDATE, DELETE
DDL (数据定义语言) 定义数据库结构 CREATE TABLE, CREATE INDEX, DROP TABLE, ALTER TABLE

ABAP与字典的关系

  • ABAP程序:处理SQL的DML部分
  • ABAP字典:处理SQL的DDL部分

🗃️ ABAP字典概念

字典定义

ABAP字典是驻留在SAP数据库中的元数据(即关于数据的数据)以及由数据库维护的元数据。

字典功能



graph TB
    A[ABAP字典] --> B[创建和管理数据定义]
    A --> C[创建表结构]
    A --> D[创建数据元素]
    A --> E[创建域]
    A --> F[创建视图]
    A --> G[定义类型]

🔧 字典中的基本类型

三种基本类型

类型 描述 用途
数据元素 定义数据类型、长度和小数位数 描述基本数据类型
结构 可以具有任何类型的组件 复合数据类型
表类型 描述内部表的结构 内部表定义

全局区域概念

字典被称为全局区域,因为:

  • 字典中的对象对所有ABAP程序都是全局的
  • ABAP程序中的数据可以通过引用这些Dictionary全局对象来声明
  • 避免了数据定义的重复

🎯 字典的主要任务

实现的功能

  1. 强制数据完整性

    • 确保数据的一致性和有效性
    • 实施业务规则和约束
  2. 管理数据定义

    • 避免数据定义的冗余
    • 统一数据标准
  3. 紧密集成

    • 与ABAP开发工作台紧密集成
    • 提供统一的开发环境

🏗️ 数据结构示例

客户数据结构示例



graph TD
    A[客户结构] --> B[名称]
    A --> C[地址]
    A --> D[电话]
    
    B --> B1[名字]
    B --> B2[姓氏]
    
    C --> C1[街道]
    C --> C2[城市]
    C --> C3[邮编]
    
    D --> D1[电话号码1]
    D --> D2[电话号码2]
    D --> D3[电话号码3]

类型说明

组件 类型 说明
名称 结构 包含名字和姓氏
地址 结构 包含街道、城市、邮编
电话 表类型 客户可以有多个电话号码

🔍 字典支持的编辑工具

搜索工具

  • 搜索帮助:帮助用户输入有效数据
  • 输入帮助:提供数据选择功能

锁定工具

  • 锁定对象:保护数据完整性
  • 锁机制:防止并发访问冲突

📝 对象激活机制

自动创建过程

当对象被激活时:

  1. 字典对象在底层数据库中自动创建
  2. 生成相应的数据库结构
  3. 创建索引和约束

开发工作台集成



graph LR
    A[ABAP开发工作台] --> B[ABAP字典]
    B --> C[数据定义]
    B --> D[数据库对象]
    B --> E[编辑工具]

💡 字典使用场景

1. 功能模块接口

1
2
3
4
* 功能模块接口参数类型定义
TYPES: customer_type TYPE scustom,
address_type TYPE scustom-address,
phone_table TYPE TABLE OF scustom-phones.

2. 数据库表定义

1
2
* 使用字典类型定义数据库表
TABLES: scustom.

3. 数据类型声明

1
2
3
* 引用字典中的数据元素
DATA: customer_name TYPE scustom-name,
city TYPE scustom-address-city.

ABAP 复制内表

📊 内表复制概述

标题行概念

当我们从带有标题行的内部表中读取记录时,该记录将从表本身移动到标题行。这是程序工作的标题行。同样适用于创建新记录 - 使用标题行将新记录发送到表体本身。

复制方式



graph TD
    A[内表复制] --> B[字段级复制]
    A --> C[结构化复制]
    
    B --> B1[MOVE语句]
    B --> B2[单个字段移动]
    
    C --> C1[MOVE-CORRESPONDING]
    C --> C2[自动匹配字段]

🔧 MOVE语句

功能

MOVE语句用于复制记录,将数据从源字段移动到目标字段。

语法

1
MOVE <table_field> TO <internal_tab_field>.

执行逻辑

选择循环每次填充一个字段,使用MOVE语句将数据从一个表的字段移动到另一个表的字段。

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
REPORT  zcuslist1.
TABLES: zcustomers1.

DATA:
BEGIN OF itab01 OCCURS 0,
name LIKE zcustomers1-name,
dob LIKE zcustomers1-dob,
END OF itab01.

* 使用SELECT循环和MOVE语句复制数据
SELECT * FROM zcustomers1.
MOVE zcustomers1-name TO itab01-name.
MOVE zcustomers1-dob TO itab01-dob.
ENDSELECT.

* 输出结果
WRITE: / itab01-name, itab01-dob.

输出示例

1
MARGARET     02.11.1994

MOVE语句特点

  1. 显式控制:需要明确指定每个字段的移动
  2. 灵活性:可以只移动部分字段
  3. 安全性:确保数据类型匹配

🔧 MOVE-CORRESPONDING语句

功能

MOVE-CORRESPONDING语句自动将数据从源结构的字段移动到目标结构中名称对应的字段。

语法

1
MOVE-CORRESPONDING <table_name> TO <internal_tab>.

执行逻辑

系统将数据从CUSTOMERS的字段移动到itab01中的相应字段,前提是两者具有匹配的字段名称。

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
REPORT  zcustomerlist.
TABLES: zcustomers1.

DATA:
BEGIN OF itab01 OCCURS 0,
customer LIKE zcustomers1-customer,
name LIKE zcustomers1-name,
title LIKE zcustomers1-title,
dob LIKE zcustomers1-dob,
END OF itab01.

* 使用MOVE-CORRESPONDING自动复制
SELECT * FROM zcustomers1.
MOVE-CORRESPONDING zcustomers1 TO itab01.
APPEND itab01.
ENDSELECT.

* 输出结果
LOOP AT itab01.
WRITE: / itab01-name, itab01-dob.
ENDLOOP.

输出示例

1
2
3
4
5
MARK           21.05.1981
JAMES 14.08.1977
AURIELE 19.06.1990
STEPHEN 22.07.1985
MARGARET 02.11.1994

MOVE-CORRESPONDING特点

  1. 自动匹配:自动匹配同名字段
  2. 简洁高效:只需一个语句完成所有匹配字段的移动
  3. 类型安全:要求两个字段具有匹配的数据类型和长度

⚠️ 重要注意事项

字段匹配要求

  • 使用MOVE-CORRESPONDING时,需要确保两个字段都具有匹配的数据类型和长度
  • 在示例中,这已经用LIKE语句预先完成

字段名称影响



graph LR
    A[源表字段] --> B[名称匹配]
    B --> C[数据复制]
    B --> D[跳过不匹配]
    
    C --> C1[成功复制]
    D --> D1[保持原值]

错误预防

1
2
3
4
5
6
7
8
* 检查字段是否存在
IF line_exists( itab01 WITH TABLE KEY name = 'Test' ).
" 字段存在,可以复制
MOVE-CORRESPONDING source TO itab01.
ELSE.
" 字段不存在,处理错误
WRITE: / '字段不存在'.
ENDIF.

💡 复制方法对比

特性 MOVE MOVE-CORRESPONDING
控制粒度 字段级 结构级
代码量 多(每个字段都要写) 少(一个语句)
灵活性 高(可选择部分字段) 低(只能匹配字段)
性能 中等 高(批量操作)
适用场景 精确控制字段移动 批量复制结构数据

🎯 最佳实践

1. 使用MOVE进行选择性复制

1
2
3
4
5
6
* 只复制需要的字段
SELECT * FROM zcustomers1.
MOVE zcustomers1-name TO itab01-name.
MOVE zcustomers1-dob TO itab01-dob.
APPEND itab01.
ENDSELECT.

2. 使用MOVE-CORRESPONDING进行批量复制

1
2
3
4
5
6
* 自动复制所有匹配字段
SELECT * FROM zcustomers1
UP TO 10 ROWS.
MOVE-CORRESPONDING zcustomers1 TO itab01.
APPEND itab01.
ENDSELECT.

3. 错误处理

1
2
3
4
5
6
7
8
9
10
11
12
13
* 复制前检查数据类型
DATA: lv_name TYPE string,
lv_dob TYPE d.

SELECT SINGLE name dob FROM zcustomers1
INTO (lv_name, lv_dob)
WHERE customer = '100001'.

IF sy-subrc = 0.
MOVE lv_name TO itab01-name.
MOVE lv_dob TO itab01-dob.
APPEND itab01.
ENDIF.

4. 性能优化

1
2
3
4
5
6
7
8
9
10
11
12
* 使用批量复制而不是循环内复制
DATA: lt_source TYPE TABLE OF zcustomers1,
lt_target LIKE itab01.

* 批量读取
SELECT * FROM zcustomers1 INTO TABLE lt_source.

* 批量复制
LOOP AT lt_source INTO DATA(wa_source).
MOVE-CORRESPONDING wa_source TO wa_target.
APPEND wa_target TO lt_target.
ENDLOOP.

🔗 高级复制技术

1. 使用ASSIGN进行动态复制

1
2
3
4
5
6
7
8
9
FIELD-SYMBOLS: <fs_source> ANY,
<fs_target> ANY.

ASSIGN COMPONENT 'NAME' OF STRUCTURE zcustomers1 TO <fs_source>.
ASSIGN COMPONENT 'NAME' OF STRUCTURE itab01 TO <fs_target>.

IF <fs_source> IS ASSIGNED AND <fs_target> IS ASSIGNED.
MOVE <fs_source> TO <fs_target>.
ENDIF.

2. 使用CORRESPONDING关键字(ABAP 7.4+)

1
2
3
4
5
* 更简洁的写法(ABAP 7.4+)
SELECT * FROM zcustomers1
INTO TABLE @DATA(lt_temp).

lt_target = CORRESPONDING #( lt_temp ).

⚠️ 常见错误

1. 字段不匹配

1
2
* 错误示例:字段名称不匹配
MOVE zcustomers1-full_name TO itab01-name. * 字段名不匹配

2. 数据类型不兼容

1
2
* 错误示例:数据类型不匹配
MOVE zcustomers1-amount TO itab01-name. * 金额不能赋值给姓名

3. 目标表未初始化

1
2
* 错误示例:目标表未定义
MOVE-CORRESPONDING zcustomers1 TO itab01. * itab01未定义

ABAP 变量

ℹ️概述
变量是用于在程序的分配的存储区域中存储值的命名数据对象。顾名思义,用户可以在ABAP语句的帮助下更改变量的内容。ABAP中的每个变量都有一个特定的类型,它决定了变量内存的大小和布局、可以存储在该存储器内的值的范围以及可以应用于该变量的一组操作。

📝 变量声明

您必须先声明所有变量,然后才能使用它们。变量声明的基本形式是:

1
DATA <f> TYPE <type> VALUE <val>.

参数说明

  • <f>:指定变量的名称(最多可包含30个字符)
  • <type>:指定变量的类型(具有完全指定的技术属性的任何数据类型)
  • <val>:指定变量的初始值
💡自动初始化
在定义基本固定长度变量的情况下,DATA语句会自动使用类型特定的初始值填充变量的值。

有效示例

1
2
3
DATA d1(2) TYPE C.
DATA d2 LIKE d1.
DATA minimum_value TYPE I VALUE 10.
📌示例说明
  • d1是C类型的变量(2个字符长度)
  • d2是d1类型的变量(继承d1的类型和长度)
  • minimum_value是ABAP整数类型的变量,初始值为10

🎭 ABAP变量类型

ABAP中有三种变量类型:

1. 静态变量 (Static Variables)

  • 在子程序、函数模块和静态方法中声明
  • 生命周期与声明的上下文相关联
  • 使用CLASS-DATA语句,可以在类中声明变量
  • 使用PARAMETERS语句声明链接到选择屏幕上的输入字段的基本数据对象
  • 使用SELECT-OPTIONS语句声明在选择屏幕上链接到输入字段的内部表

2. 参考变量 (Reference Variables)

  • 使用REF TO附加声明引用变量
  • 引用变量的静态类型限制其可以引用的对象集合
  • 静态类型总是更加通用或与动态类型相同

3. 系统变量 (System Variables)

  • ABAP系统变量可从所有ABAP程序访问
  • 这些字段实际上由运行时环境填充
  • 这些字段中的值指示系统在任何给定时间点的状态
  • 可以在SAP中的SYST表中找到系统变量的完整列表

📋 命名约定

以下是命名变量时使用的约定:

约束 说明
不能使用特殊字符 不能使用特殊字符(如”%”和”=”)来命名变量
不能更改预定义对象名称 无法更改预定义数据对象的名称
不能与关键字相同 变量的名称不能与任何ABAP关键字或子句相同
名称要有意义 变量的名称必须传达变量的含义,而不需要进一步的评论
避免使用连字符 连字符被保留以表示结构的组件,应避免在变量名中使用
使用下划线分隔 下划线字符可用于分隔复合词
💥PARAMETERS语句示例
1
2
3
4
5
REPORT ZTest123_01.
PARAMETERS: NAME(10) TYPE C,
CLASS TYPE I,
SCORE TYPE P DECIMALS 2,
CONNECT TYPE MARA-MATNR.

参数说明

参数 类型 描述
NAME C(10) 10个字符的字符串参数
CLASS I 整数类型的参数
SCORE P DECIMALS 2 具有两个小数位的打包十进制数
CONNECT MARA-MATNR 引用ABAP字典的MARA-MATNR类型

🔗 参考变量语法

声明引用变量的语法是:

1
DATA <ref> TYPE REF TO <type> VALUE IS INITIAL.

语法说明

  • REF TO:附加声明一个引用变量
  • 静态类型:指定引用变量的静态类型,限制其可以引用的对象集合
  • 动态类型:引用变量当前引用的数据类型或类
  • 静态类型总是更加通用或与动态类型相同

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
CLASS C1 DEFINITION.
PUBLIC SECTION.
DATA Bl TYPE I VALUE 1.
ENDCLASS.

DATA: Oref TYPE REF TO C1,
Dref1 LIKE REF TO Oref,
Dref2 TYPE REF TO I.

CREATE OBJECT Oref.
GET REFERENCE OF Oref INTO Dref1.
CREATE DATA Dref2.
Dref2->* = Dref1->*->Bl.
📌代码解析
  • 声明了对象引用Oref和两个数据引用变量Dref1和Dref2
  • 这两个数据引用变量都是完全类型化的
  • 可以使用取消引用运算符->*在操作数位置取消引用

🌐 系统变量

特点

  • ABAP系统变量可从所有ABAP程序访问
  • 这些字段实际上由运行时环境填充
  • 这些字段中的值指示系统在任何给定时间点的状态
  • 可以使用SYST-SY-访问SYST结构的各个字段

常用系统变量

系统变量 描述
SY-ABCDE 字母表
SY-DATUM 当前日期
SY-DBSYS 数据库系统
SY-HOST 主机名
SY-LANGU 语言
SY-MANDT 客户端
SY-OPSYS 操作系统
SY-SAPRL SAP版本级别
SY-SYSID 系统ID
SY-TCODE 事务代码
SY-UNAME 用户名
SY-UZEIT 当前时间

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
REPORT Z_Test123_01.

WRITE: /'SY-ABCDE', SY-ABCDE,
/'SY-DATUM', SY-DATUM,
/'SY-DBSYS', SY-DBSYS,
/'SY-HOST ', SY-HOST,
/'SY-LANGU', SY-LANGU,
/'SY-MANDT', SY-MANDT,
/'SY-OPSYS', SY-OPSYS,
/'SY-SAPRL', SY-SAPRL,
/'SY-SYSID', SY-SYSID,
/'SY-TCODE', SY-TCODE,
/'SY-UNAME', SY-UNAME,
/'SY-UZEIT', SY-UZEIT.

输出示例

1
2
3
4
5
6
7
8
9
10
11
12
SY-ABCDE ABCDEFGHIJKLMNOPQRSTUVWXYZ
SY-DATUM 12.09.2015
SY-DBSYS ORACLE
SY-HOST sapserver
SY-LANGU EN
SY-MANDT 800
SY-OPSYS Windows NT
SY-SAPRL 700
SY-SYSID DMO
SY-TCODE SE38
SY-UNAME SAPUSER
SY-UZEIT 14:25:48

🔄 ABAP变量类型关系图



graph TD
    A[ABAP变量] --> B[静态变量]
    A --> C[参考变量]
    A --> D[系统变量]
    
    B --> B1[子程序变量]
    B --> B2[函数模块变量]
    B --> B3[类静态变量]
    B --> B4[PARAMETERS变量]
    B --> B5[SELECT-OPTIONS变量]
    
    C --> C1[对象引用]
    C --> C2[数据引用]
    
    D --> D1[系统状态变量]
    D --> D2[运行时环境变量]
    D --> D3[常量系统字段]

💡 最佳实践

  1. 变量命名规范

    • 使用有意义的名称
    • 避免使用特殊字符
    • 使用下划线分隔单词
    • 不要使用连字符
  2. 变量声明

    • 先声明后使用
    • 指定适当的数据类型
    • 设置有意义的初始值
  3. 系统变量使用

    • 了解常用系统变量的含义
    • 在适当的地方使用系统变量
    • 注意系统变量的只读特性

ABAP 封装

📖 简介

封装(Encapsulation)是一种面向对象编程(OOP)核心概念,它将数据和功能绑定在一起,操作数据,并保持两者免受外部干扰和误用。

🔄 封装的核心概念

  • 数据封装:将数据和相关功能打包在一起
  • 数据隐藏:保护内部数据不被外部直接访问
  • 接口暴露:只对外暴露必要的接口
  • 实现隔离:隐藏内部实现细节

💡 重要提示 - 数据封装导致了数据隐藏的重要OOP概念。封装是捆绑数据和使用它们的函数的机制,数据抽象是一种仅暴露接口并向用户隐藏实现细节的机制。

🔒 访问控制

ABAP中的访问修饰符

修饰符 访问级别 描述
PRIVATE 私有 仅在类内部可见
PROTECTED 受保护 在类及其子类中可见
PUBLIC 公共 外部可访问

默认行为

ABAP默认规则

  • 类中定义的所有项目默认都是私有的
  • 这种设计强制良好的封装实践


graph TB
    A[类内部] -->|可访问| B[Private成员]
    A -->|可访问| C[Protected成员]
    A -->|可访问| D[Public成员]
    E[外部访问] -->|不可访问| B
    E -->|不可访问| C
    E -->|可访问| D

📝 接口封装示例

封装实际上意味着一个属性和方法可以在不同的类中修改。因此,数据和方法可以具有不同的形式和逻辑,可以隐藏以分离类。

🔗 接口封装原理

当我们需要在不同的类中创建具有不同功能的一个方法时,使用接口。这里方法的名称不需要改变。相同的方法必须在不同的类实现中实现。

📊 示例程序

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
REPORT zencap1.

INTERFACE inter_1.
DATA text1 TYPE char35.
METHODS method1.
ENDINTERFACE.

CLASS class1 DEFINITION.
PUBLIC SECTION.
INTERFACES inter_1.
ENDCLASS.

CLASS class2 DEFINITION.
PUBLIC SECTION.
INTERFACES inter_1.
ENDCLASS.

CLASS class1 IMPLEMENTATION.
METHOD inter_1~method1.
inter_1~text1 = 'Class 1 Interface method'.
WRITE / inter_1~text1.
ENDMETHOD.
ENDCLASS.

CLASS class2 IMPLEMENTATION.
METHOD inter_1~method1.
inter_1~text1 = 'Class 2 Interface method'.
WRITE / inter_1~text1.
ENDMETHOD.
ENDCLASS.

START-OF-SELECTION.
DATA:
object1 TYPE REF TO class1,
object2 TYPE REF TO class2.

CREATE OBJECT:
object1,
object2.
CALL METHOD:
object1->inter_1~method1,
object2->inter_1~method1.

📝 输出结果

1
2
Class 1 Interface method
Class 2 Interface method

🔍 代码解析



graph LR
    A[接口inter_1] --> B[Class1]
    A --> C[Class2]
    B --> D[实现method1]
    C --> D

  1. 接口定义:声明了公共的接口契约
  2. 类实现:两个类都实现了相同接口
  3. 不同实现:相同的方法名,不同的实现逻辑
  4. 统一调用:通过相同的接口引用调用不同实现

🏗️ 封装的优势

📈 降低耦合度

封装的类对外界没有很多依赖。此外,他们与外部客户端的交互通过稳定的公共接口进行控制。也就是说,封装类及其客户端松散耦合。

🔧 提高可重用性

在大多数情况下,具有定义良好的接口的类可以插入到另一个上下文中。当设计正确时,封装类成为可重用的软件资产。

🛡️ 增强可维护性

  • 内部修改:内部实现可以随时修改而不影响外部调用者
  • 接口稳定:对外暴露的接口保持稳定
  • 减少副作用:内部变化不会影响外部代码

💡 设计策略

最佳实践

  1. 默认私有化

    1
    2
    3
    4
    5
    6
    7
    8
    CLASS my_class DEFINITION.
    PRIVATE SECTION.
    DATA: internal_data TYPE string.
    PROTECTED SECTION.
    METHODS: internal_method.
    PUBLIC SECTION.
    METHODS: public_method.
    ENDCLASS.
  2. 最小暴露原则

    • 只暴露必要的接口
    • 避免暴露内部实现细节
    • 限制外部访问权限
  3. 接口设计

    • 设计稳定且易于理解的接口
    • 保持接口的向后兼容性
    • 避免频繁变更公共接口

📋 设计检查清单

设计原则 实践方式
数据隐藏 使用private修饰符保护数据
接口稳定 避免频繁修改公共接口
功能内聚 相关功能放在同一类中
低耦合 最小化类之间的依赖关系

🎯 应用场景

1. 数据保护

1
2
3
4
5
6
7
8
CLASS bank_account DEFINITION.
PRIVATE SECTION.
DATA: balance TYPE p decimals 2.
METHODS: validate_amount IMPORTING amount TYPE p decimals 2.
PUBLIC SECTION.
METHODS: deposit IMPORTING amount TYPE p decimals 2,
get_balance RETURNING VALUE(result) TYPE p decimals 2.
ENDCLASS.

2. 业务逻辑封装

1
2
3
4
5
6
7
8
9
10
CLASS order_processor DEFINITION.
PRIVATE SECTION.
DATA: order_data TYPE order_struct.
METHODS: validate_order,
calculate_tax,
process_payment.
PUBLIC SECTION.
METHODS: create_order IMPORTING order_data_in TYPE any,
get_order_status RETURNING VALUE(status) TYPE string.
ENDCLASS.

3. 工厂模式实现

1
2
3
4
5
6
7
8
CLASS factory DEFINITION.
PUBLIC SECTION.
CLASS-METHODS: create_object IMPORTING obj_type TYPE string
RETURNING VALUE(obj) TYPE ref to any.
PRIVATE SECTION.
CLASS-METHODS: create_specific_object IMPORTING obj_type TYPE string
RETURNING VALUE(obj) TYPE ref to any.
ENDCLASS.

ABAP 宏

🔄 宏概述

定义

宏是基于占位符设计的代码段,用于在程序中多次重复使用同一组语句。

宏的特点

  • 程序级作用域:只能在定义它的程序中使用宏
  • 预处理器替换:在编译时被实际的ABAP语句替换
  • 基于占位符:使用类似于C语言指针的占位符机制

宏的适用场景

  • 长计算
  • 复杂的WRITE语句
  • 重复的代码模式
  • 格式化输出

📝 宏语法

基本语法

1
2
3
4
5
6
DEFINE <macro_name>.
<statements>
END-OF-DEFINITION.

......
<macro_name> [<param1> <param2>....].

占位符机制

1
2
3
DEFINE <macro_name>.
WRITE: 'This is Macro &1'.
END-OF-OF-DEFINITION.
占位符 替换规则 示例
&1, &2, …, &9 参数位置 &1替换第一个参数
& 单个&字符 &&替换为&

🛠️ 创建和使用宏

步骤1:定义宏

在程序开始部分定义宏,必须在使用宏之前完成定义。

1
2
3
4
5
6
REPORT ZMACRO_TEST.

* 定义宏
DEFINE mac_test.
WRITE: 'This is Macro &1'.
END-OF-DEFINITION.

步骤2:使用宏

在程序中调用宏并传入参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
* 定义参数
PARAMETERS: s1 TYPE c AS CHECKBOX,
s2 TYPE c AS CHECKBOX,
s3 TYPE c AS CHECKBOX DEFAULT 'X'.

* 调用宏
START-OF-SELECTION.
IF s1 = 'X'.
mac_test 1.
ENDIF.
IF s2 = 'X'.
mac_test 2.
ENDIF.
IF s3 = 'X'.
mac_test 3.
ENDIF.

💡 宏的示例

1. 基本宏示例

1
2
3
4
5
6
7
8
9
10
11
12
REPORT z_macro_basic.

* 定义格式化宏
DEFINE format_header.
WRITE: / '---------------------------------'.
WRITE: / &1.
WRITE: / '---------------------------------'.
END-OF-DEFINITION.

* 使用宏
format_header '客户列表'.
format_header '供应商列表'.

2. 多参数宏示例

1
2
3
4
5
6
7
8
9
10
11
REPORT z_macro_params.

* 定义带多个参数的宏
DEFINE display_info.
WRITE: / '姓名:', &1, '年龄:', &2, '部门:', &3.
END-OF-DEFINITION.

* 使用宏
display_info '张三' '25' '财务部'.
display_info '李四' '30' '技术部'.
display_info '王五' '28' '市场部'.

3. 条件判断宏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
REPORT z_macro_conditional.

* 定义条件宏
DEFINE write_if_not_empty.
IF &1 IS NOT INITIAL.
WRITE: / &2.
ENDIF.
END-OF-DEFINITION.

* 使用宏
DATA: lv_name TYPE string VALUE '张三',
lv_empty TYPE string,
lv_result TYPE string.

write_if_not_empty lv_name '客户姓名: 张三'.
write_if_not_empty lv_empty '空值不显示'.

4. 表格格式化宏

1
2
3
4
5
6
7
8
9
10
11
12
REPORT z_macro_table.

* 定义表格宏
DEFINE table_header.
ULINE.
WRITE: / sy-ULINE(10) COLOR COL_HEADING, &1 COLOR COL_HEADING.
ULINE.
END-OF-DEFINITION.

* 使用宏
table_header '客户信息'.
WRITE: / 'ID' COLOR COL_KEY, '姓名' COLOR COL_KEY, '余额' COLOR COL_KEY.

⚠️ 宏的限制

1. 占位符数量限制

  • 占位符的最大数量为9(&1 到 &9)
  • 超过9个参数需要使用其他方法

2. 作用域限制

  • 只能在定义它的程序中使用
  • 不能跨程序使用宏

3. 调用限制

  • 可以在一个宏中调用另一个宏
  • 不能调用同一个宏(避免无限递归)

4. 代码可读性

  • 过度使用宏可能降低代码可读性
  • 调试时宏已被替换,不易跟踪

🎯 宏的最佳实践

1. 命名规范

1
2
3
4
5
6
7
8
9
* 好的命名
DEFINE format_currency_amount.
WRITE: &1 CURRENCY 'CNY'.
END-OF-DEFINITION.

* 避免的命名
DEFINE write.
WRITE: &1.
END-OF-DEFINITION. " 过于通用

2. 宏组织

1
2
3
4
5
6
7
8
9
10
11
12
* 将相关宏放在一起
DEFINE display_header.
" 头部显示宏
END-OF-DEFINITION.

DEFINE display_footer.
" 底部显示宏
END-OF-DEFINITION.

DEFINE format_table.
" 表格格式化宏
END-OF-DEFINITION.

3. 参数验证

1
2
3
4
5
6
7
8
* 在宏内进行参数验证
DEFINE safe_write.
IF &1 IS NOT INITIAL.
WRITE: / &2.
ELSE.
WRITE: / '警告: 参数为空'.
ENDIF.
END-OF-DEFINITION.

🔧 宏 vs 子程序

特性 子程序
作用域 仅定义程序内 整个系统
执行方式 预处理器替换 运行时调用
参数 最多9个 无限制
调试 困难(已替换) 容易
重用性 低(程序内) 高(全局)
性能 高(无调用开销) 低(有调用开销)

💡 使用场景建议

适合使用宏的场景

  1. 简单的格式化输出
  2. 重复的代码模式
  3. 短小的功能片段
  4. 性能敏感的代码

适合使用子程序的场景

  1. 复杂的功能逻辑
  2. 需要错误处理的代码
  3. 跨程序重用的代码
  4. 需要调试的代码
0%