Mark的技术博客

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

吴让宇(Mark)

吴让宇(Mark)

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

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

SAP Fiori ABAP CDS OData Dify Docker AI LLM

最新文章

ABAP 常量和文本值

ℹ️概述
文字是您在程序的源代码中创建的未命名数据对象,他们完全由他们的价值定义,您不能更改文字的值。常量是通过使用声明性语句静态创建的命名数据对象,分配给常数的值在程序执行期间不能更改。

📝 文字类型

文字有两种基本类型:

🔢 数字字面量

数字文字是可以有前缀符号的数字序列。在数字文字中,没有十进制分隔符,也没有带尾数和指数的符号。

1
183.  -97.  +326.

🔤 字符文字

字符文字是用单引号括起来的ABAP程序的源代码中的字母数字字符序列。

1. 文本字段文字 (类型C)

用单引号括起的字符文字具有预定义的ABAP类型C。

1
2
3
REPORT YR_SEP_12.
Write 'Tutorials Point'.
Write / 'ABAP Tutorial'.

2. 字符串文字 (类型STRING)

包含在后引号中的文字具有ABAP类型STRING。

1
2
3
REPORT YR_SEP_12.
Write `Tutorials Point `.
Write / `ABAP Tutorial `.

⚠️ 重要区别

特性 文本字段文字 字符串文字
引号类型 单引号 ' 后引号 `
数据类型 C STRING
尾随空白 被忽略 被保留
输出示例 Tutorials Point Tutorials Point
📌输出结果
上述两种情况的输出相同:
1
2
Tutorials Point
ABAP Tutorial

🎯 CONSTANTS声明

语法格式

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

参数说明

  • <f> - 指定常量的名称
  • <type> - 表示常量的类型,继承与现有数据类型相同的技术属性
  • <val> - 为声明的常量名<f>赋值一个初始值
⚠️必须使用VALUE子句
我们应该使用CONSTANTS语句中的VALUE子句。子句”VALUE”用于在声明期间为常量赋予初始值。

CONSTANTS与DATA的区别

特性 DATA CONSTANTS
值可变性 可更改 不可更改
声明方式 DATA CONSTANTS
初始化 可选 必须指定
用途 存储变量数据 存储固定值

📊 常量类型

1. 基本常量

涉及基本数据类型的常量。

1
2
3
REPORT YR_SEP_12.
CONSTANTS PQR TYPE P DECIMALS 4 VALUE '1.2356'.
Write: / 'The value of PQR is:', PQR.

输出

1
The value of PQR is: 1.2356

2. 复杂常量

由多个字段组成的结构化常量。

1
2
3
4
5
BEGIN OF EMPLOYEE,
Name(25) TYPE C VALUE 'Management Team',
Organization(40) TYPE C VALUE 'Tutorials Point Ltd',
Place(10) TYPE C VALUE 'India',
END OF EMPLOYEE.
💡代码解析
EMPLOYEE是一个由Name、Organization和Place字段组成的复杂常量。

3. 参考常量

引用其他对象或数据的常量。

1
CONSTANTS null_pointer TYPE REF TO object VALUE IS INITIAL.
📌参考常量用途
我们可以在比较中使用常量引用,或者我们可以将其传递给过程。

🔧 常量使用示例

实际应用场景

1. 配置常量

1
2
3
4
5
" 应用程序配置常量
CONSTANTS:
MAX_RETRIES TYPE I VALUE 3,
TIMEOUT_SECS TYPE I VALUE 30,
DEBUG_MODE TYPE C VALUE 'X'.

2. 业务规则常量

1
2
3
4
5
" 业务规则
CONSTANTS:
TAX_RATE TYPE P DECIMALS 2 VALUE '0.18',
DISCOUNT_THRESHOLD TYPE I VALUE 1000,
CURRENCY_CODE TYPE C VALUE 'CNY'.

3. 系统常量

1
2
3
4
5
" 系统参数
CONSTANTS:
COMPANY_CODE TYPE C VALUE '1000',
FISCAL_YEAR TYPE N VALUE '2024',
DECIMAL_SEPARATOR TYPE C VALUE '.'.

🎨 常量命名规范

推荐的命名模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
✅ 使用大写字母和下划线:
MAX_LENGTH
DEFAULT_VALUE
ERROR_MESSAGE

✅ 带有业务含义的常量:
TAX_RATE_VAT
DISCOUNT_SENIOR
CURRENCY_USD

✅ 系统配置常量:
DB_HOST_NAME
API_TIMEOUT
MAX_LOGIN_ATTEMPTS

常量的作用域

作用域 声明位置 特点
全局常量 在全局声明部分 整个程序可见
局部常量 在子程序/方法中 仅在声明范围内可见
类常量 在类定义中 类的静态属性

🔄 常量与变量的对比



graph TD
    A[数据对象] --> B[变量]
    A --> C[常量]
    
    B --> B1[可修改]
    B --> B2[使用DATA声明]
    B --> B3[无初始值]
    
    C --> C1[不可修改]
    C --> C2[使用CONSTANTS声明]
    C --> C3[必须有初始值]

最佳实践

  1. 何时使用常量

    • 固定值配置
    • 业务规则
    • 系统参数
    • 常用字符串
  2. 避免使用常量的情况

    • 需要频繁变化的值
    • 用户输入的数据
    • 运行时计算的结果

💡 最佳实践

1. 常量声明原则

  • 常量名称要有意义,使用大写字母
  • 始终指定数据类型
  • 为常量提供有初始值
  • 遵循命名规范

2. 使用场景选择

  • 使用常量而不是硬编码值
  • 将常量集中管理
  • 常量值变更时只需修改一处
  • 使用常量提高代码可读性

3. 性能考虑

  • 常量在编译时就确定值
  • 运行时访问常量比变量更快
  • 常量不占用运行时内存

ABAP 数据元素

📋 概述

数据元素描述ABAP数据字典中的各个字段。它们是复杂类型的最小不可分割单元,用于定义:

  • 表字段的类型
  • 表的结构组件
  • 行类型

语义定义

数据元素不仅定义技术规格,还提供:

  • 关于表字段的含义信息
  • 屏幕字段的编辑信息
  • 自动可用于引用数据元素的所有屏幕字段

基本类型 vs 引用类型



graph TD
    A[数据元素] --> B[基本类型]
    A --> C[引用类型]
    
    B --> B1[直接定义类型]
    B --> B2[分配域]
    
    C --> C1[引用其他数据元素]
    C --> C2[引用预定义类型]

🛠️ 创建数据元素

步骤1:准备工作

在创建新数据元素之前,需要检查任何现有的数据元素是否具有表字段中所需的相同语义规范。如果有,应该使用现有的数据元素。

步骤2:启动事务

使用事务 SE11 进入ABAP字典。

步骤3:选择数据元素类型

在ABAP字典的初始屏幕中选择数据类型的单选按钮:



graph LR
    A[SE11事务] --> B[选择数据类型单选按钮]
    B --> C[输入数据元素名称]
    C --> D[点击CREATE按钮]

命名规则

  • 对象名称始终以 “Z”“Y” 开头
  • 确保在客户名称空间下创建

步骤4:确认类型选择

在CREATE TYPE弹出窗口中选择”数据元素”单选按钮,然后点击绿色复选标记图标。

步骤5:输入基本信息

数据元素维护屏幕

  • 在短文本字段中输入说明(如”客户数据元素”)
  • 注意:必须先输入短文本,才能输入其他属性

步骤6:分配类型

数据元素可以通过以下方式分配类型:

分配方式 说明 示例
基本类型 直接定义基本数据类型 CHAR, NUMC, DEC
引用类型 引用其他数据元素 REFERENCE TO OTHER_DTEL
分配域 分配给域 DOMAIN ZCUST

创建基本数据元素



graph TB
    A[数据元素] --> B[选择域]
    A --> C[选择预定义类型]
    
    B --> B1[ZCUST域]
    C --> C1[CHAR类型]
    C --> C2[NUMC类型]

步骤7:设置字段标签

在字段标签选项卡中输入:

标签类型 描述 用途
短文本 简短的描述 列表显示
中等文本 中等长度的描述 工具提示
长文本 详细的描述 帮助文档
标题 标题文本 屏幕标题

自动生成长度

按Enter键,系统会自动为这些标签生成合适的长度。

步骤8:保存对象

  1. 保存更改

    • 将显示”创建对象目录条目”弹出窗口
    • 输入包名称或使用本地对象
  2. 激活数据元素

    • 点击激活图标(火柴图标)
    • 或按 Ctrl + F3

步骤9:激活处理

激活过程

  1. 激活名为”DTEL”的顶部条目(如Z_CUST)
  2. 点击绿色勾号按钮
  3. 状态栏显示”对象已激活”

激活日志

如果出现错误或警告:

  • 系统自动显示激活日志
  • 可以通过实用程序(M)→激活日志查看

🔍 数据元素属性详解

基本属性

属性 描述 示例
短文本 数据元素的简短描述 “客户编号”
数据类型 基础数据类型 CHAR, NUMC, DEC
长度 字符长度 10, 20, 40
小数位 小数位数 2, 3

引用属性

属性 描述 作用
引用类型 引用的对象类型 基础类型或引用类型
域名 关联的域名称 技术规格定义
数据类型 ABAP数据类型 技术实现

字段标签

标签 长度 用途
短文本 40字符 列表显示
中等文本 60字符 工具提示
长文本 80字符 帮助文本
标题 60字符 屏幕标题

💡 数据元素的使用场景

1. 表字段定义

1
2
3
4
5
* 使用数据元素定义表字段
TABLES: scustom.
DATA: customer_id TYPE scustom-id,
name TYPE scustom-name,
address TYPE scustom-address.

2. 变量声明

1
2
3
4
* 使用数据元素声明变量
DATA: order_number TYPE vbeln, " 销售订单号
material TYPE matnr, " 物料编号
quantity TYPE mengt_field. " 数量字段

3. 参数定义

1
2
3
* 使用数据元素定义参数
PARAMETERS: p_carrid TYPE spfli-carrid, " 航空公司ID
p_connid TYPE spfli-connid. " 航班连接ID

🎯 数据元素的优势

1. 语义一致性

  • 为相同的业务概念使用相同的数据元素
  • 确保数据含义的一致性

2. 维护效率

  • 更改数据元素属性自动更新所有引用
  • 减少重复维护工作

3. 用户友好性

  • 提供有意义的字段标签
  • 改善用户界面体验

⚠️ 注意事项

1. 重用原则

  • 优先使用现有数据元素
  • 避免重复创建相同语义的数据元素

2. 命名规范

  • 使用有意义的名称
  • 遵循Z或Y前缀规则

3. 激活要求

  • 必须先保存短文本
  • 激活前检查所有属性
  • 激活失败时查看日志

ABAP 日期和时间

ℹ️概述
ABAP毫不保留地引用了公历日历,在世界大部分地区有效。我们可以将输出转换为特定国家/地区的日历。日期是指定到关于日历的精确日、周或月的时间。时间被指定为相对于一天的精确秒或分钟。ABAP总是以24小时格式保存时间。输出可以具有特定于国家的格式。日期和时间通常被解释为在当前时区有效的本地日期。

🔧 数据类型

ABAP提供了两种内置类型来处理日期和时间:

基本数据类型

1
2
3
4
5
6
7
8
9
DATA: 
date TYPE D, -- 日期类型
time TYPE T, -- 时间类型
year TYPE I, -- 年份
month TYPE I, -- 月份
day TYPE I, -- 日期
hour TYPE I, -- 小时
minute TYPE I, -- 分钟
second TYPE I. -- 秒数

数据类型格式

数据类型 格式 说明 示例
D YYYYMMDD 固定长度字符类型 20100913 表示 2010年9月13日
T HHMMSS 固定长度字符类型 102305 表示 10:23:05

⏰ 时间戳类型

除了这些内置类型,其他两种类型用于存储UTC格式的时间戳:

编号 数据类型 说明
1 TIMESTAMP 类型P - 长度8无小数
格式:YYYYMMDDhhmmss
表示短时间戳
2 TIMESTAMPL 类型P - 长度11小数7
格式:YYYYMMDDhhmmss,mmmuuun
表示长时间戳,包含秒的分数

📅 当前日期和时间

获取系统日期

1
2
3
4
5
6
7
8
REPORT YR_SEP_15.
DATA: date_1 TYPE D.
date_1 = SY-DATUM. -- 系统日期
Write: / 'Present Date is:', date_1 DD/MM/YYYY.

-- 增加日期
date_1 = date_1 + 06.
Write: / 'Date after 6 Days is:', date_1 DD/MM/YYYY.

输出

1
2
Present Date is: 21.09.2015
Date after 6 Days is: 27.09.2015
📌日期计算说明
变量date_1分配有当前系统日期SY-DATUM的值。对于ABAP中的日期计算,这意味着我们将日期对象的日期组件增加6天。ABAP运行时环境足够聪明,可以在每月结束时翻转日期值。

时间计算

1
2
3
4
5
6
7
8
REPORT YR_SEP_15.
DATA: time_1 TYPE T.
time_1 = SY-UZEIT. -- 系统时间
Write /(60) time_1 USING EDIT MASK 'Now the Time is: __:__:__'.

-- 增加时间(75秒)
time_1 = time_1 + 75.
Write /(60) time_1 USING EDIT MASK 'A Minute and a Quarter from Now, it is: __:__:__'.

输出

1
2
Now the Time is: 11:45:05
A Minute and a Quarter from Now, it is: 11:46:20

🕐 时间戳操作

使用GET TIME STAMP

1
2
3
4
5
6
7
8
9
10
11
REPORT YR_SEP_12.
DATA:
stamp_1 TYPE TIMESTAMP, -- 短时间戳
stamp_2 TYPE TIMESTAMPL. -- 长时间戳

-- 获取当前时间戳
GET TIME STAMP FIELD stamp_1.
Write: / 'The short time stamp is:', stamp_1 TIME ZONE SY-ZONLO.

GET TIME STAMP FIELD stamp_2.
Write: / 'The long time stamp is:', stamp_2 TIME ZONE SY-ZONLO.

输出

1
2
The short time stamp is: 18.09.2015 11:19:40
The long time stamp is: 18.09.2015 11:19:40,9370000
📌时区处理
在上面的示例中,我们使用TIME ZONE添加WRITE语句显示时间戳。此添加根据指定的时区的规则格式化时间戳的输出。系统字段SY-ZONLO用于显示在用户首选项中配置的本地时区。

🔍 日期时间函数

1. 日期格式化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
DATA: 
ld_date TYPE D VALUE '20240315',
ld_char(10).

-- 格式化为 DD/MM/YYYY
ld_char = ld_date+6(2) && '/' && ld_date+4(2) && '/' && ld_date(4).
Write: / 'Formatted Date:', ld_char. -- 15/03/2024

-- 使用转换函数
CALL FUNCTION 'CONVERT_DATE_TO_EXTERNAL'
EXPORTING
date_internal = ld_date
IMPORTING
date_external = ld_char.

Write: / 'External Format:', ld_char.

2. 时间计算

1
2
3
4
5
6
7
8
9
10
11
12
13
DATA: 
ld_date TYPE D,
ld_days TYPE I VALUE 30,
ld_result TYPE D.

ld_date = sy-datum.
-- 增加30天
ld_result = ld_date + ld_days.
Write: / 'After 30 days:', ld_result.

-- 计算2周前
ld_result = ld_date - 14.
Write: / '2 weeks ago:', ld_result.

3. 时间差计算

1
2
3
4
5
6
7
8
DATA: 
ld_date1 TYPE D VALUE '20240301',
ld_date2 TYPE D VALUE '20240331',
ld_days_diff TYPE I.

-- 计算天数差
ld_days_diff = ld_date2 - ld_date1.
Write: / 'Days between dates:', ld_days_diff.

🌐 时区处理

获取时区信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
DATA: 
lv_time_zone TYPE TIMEZONE,
lv_timestamp TYPE TIMESTAMP.

-- 获取系统时区
lv_time_zone = sy-zonlo.
Write: / 'System Time Zone:', lv_time_zone.

-- 转换时区
GET TIME STAMP FIELD lv_timestamp.
CONVERT TIME STAMP lv_timestamp
TIME ZONE 'UTC'
INTO DATE DATA(ld_date_utc)
TIME DATA(ld_time_utc).

Write: / 'UTC Date:', ld_date_utc.
Write: / 'UTC Time:', ld_time_utc.

时区转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
DATA: 
lv_timestamp TYPE TIMESTAMP,
lv_date TYPE D,
lv_time TYPE T,
lv_tzone TYPE TIMEZONE VALUE 'CET'. -- 中欧时间

-- 获取当前时间戳
GET TIME STAMP FIELD lv_timestamp.

-- 转换为指定时区
CONVERT TIME STAMP lv_timestamp
TIME ZONE lv_tzone
INTO lv_date lv_time.

Write: / 'Time in CET:', lv_date, lv_time.

📊 实际应用场景

1. 业务日期处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
" 计算工作日
DATA:
ld_start_date TYPE D VALUE '20240301',
ld_end_date TYPE D,
ld_work_days TYPE I.

ld_end_date = ld_start_date + 30. -- 项目截止日期

" 计算工作日(排除周末)
DO 31 TIMES.
IF ld_start_date+6(2) CO '012345' AND " 周一到周五
ld_start_date+6(2) <> '00'. " 不是周末
ADD 1 TO ld_work_days.
ENDIF.
ADD 1 TO ld_start_date.
ENDDO.

Write: / 'Total Work Days:', ld_work_days.

2. 时间戳记录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
" 记录操作时间戳
DATA:
lv_timestamp TYPE TIMESTAMP,
lv_operation_log TYPE TABLE OF tsmd_log.

" 获取操作开始时间
GET TIME STAMP FIELD lv_timestamp.

" 记录日志
APPEND VALUE #(
uname = sy-uname
timestampl = lv_timestamp
action = 'CREATE_ORDER'
) TO lv_operation_log.

3. 日期验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
" 验证日期有效性
DATA:
ld_input_date TYPE D VALUE '20240230',
lv_valid TYPE ABAP_BOOL.

" 检查日期是否有效
CALL FUNCTION 'DATE_CHECK_PLAUSIBILITY'
EXPORTING
date = ld_input_date
IMPORTING
plausible = lv_valid.

IF lv_valid = abap_true.
Write: / 'Date is valid'.
ELSE.
Write: / 'Date is invalid'.
ENDIF.

⚠️ 注意事项

1. 日期范围

1
2
3
4
5
6
" ❌ 危险:可能产生无效日期
DATA: ld_date TYPE D.
ld_date = '20241301'. -- 无效的月份

" ✅ 安全:使用系统日期
ld_date = sy-datum.

2. 时区转换

1
2
3
4
5
6
7
8
" 注意时区对时间计算的影响
DATA:
lv_utc_time TYPE T VALUE '235959',
lv_local_time TYPE T.

" 考虑时区差异进行转换
" 例如UTC+8时区,需要加8小时
lv_local_time = lv_utc_time + 80000.

3. 时间戳精度

1
2
3
4
5
6
7
8
9
10
11
" TIMESTAMP精度:秒
" TIMESTAMPL精度:微秒
DATA:
lv_short TYPE TIMESTAMP,
lv_long TYPE TIMESTAMPL.

GET TIME STAMP FIELD lv_short.
GET TIME STAMP FIELD lv_long.

Write: / 'Short precision:', lv_short.
Write: / 'Long precision:', lv_long.

💡 性能优化

1. 批量日期处理



graph LR
    A[单条日期处理] --> B[性能问题]
    B --> C[批量处理优化]
    C --> D[减少函数调用]
    C --> E[使用内表循环]

2. 缓存常用日期

1
2
3
4
5
6
7
8
9
" 缓存频繁使用的日期
DATA: gv_current_date TYPE D VALUE sy-datum.
DATA: gv_fiscal_year TYPE N VALUE sy-datum(4).

" 使用缓存而不是每次都获取
IF gv_current_date <> sy-datum.
gv_current_date = sy-datum.
gv_fiscal_year = sy-datum(4).
ENDIF.

3. 时区处理优化

1
2
3
4
5
6
7
8
9
10
" 减少时区转换次数
DATA:
lv_timezone TYPE TIMEZONE VALUE sy-zonlo,
lv_timestamp TYPE TIMESTAMP.

" 一次性转换
GET TIME STAMP FIELD lv_timestamp.
CONVERT TIME STAMP lv_timestamp
TIME ZONE lv_timezone
INTO DATA(ld_date) DATA(ld_time).

💡 最佳实践

1. 日期处理原则

  • 使用标准数据类型(D、T)
  • 验证日期的有效性
  • 注意闰年和其他特殊情况

2. 时间处理原则

  • 使用24小时格式
  • 考虑时区差异
  • 正确处理时间溢出

3. 时间戳使用

  • 根据精度需求选择类型
  • 考虑存储空间
  • 注意UTC与本地时间的转换

4. 性能考虑

  • 减少不必要的日期时间转换
  • 使用批量处理
  • 缓存频繁使用的日期时间值

ABAP 搜索帮助

🔍 搜索帮助概述

定义

搜索帮助是ABAP字典的另一个存储库对象,用于以列表的形式显示字段的所有可能值。此列表也称为命中列表

主要功能

  • 替代手动输入:可以选择从命中列表中输入值
  • 提高效率:避免冗长乏味的输入过程
  • 减少错误:防止手动输入时出现错误

🎯 搜索帮助的作用



graph LR
    A[用户输入] --> B[搜索帮助]
    B --> C[命中列表]
    C --> D[选择值]
    D --> E[自动填充]
    E --> F[数据验证]

🛠️ 创建搜索帮助

步骤1:启动事务

使用事务 SE11,选择”搜索帮助”的单选按钮,输入要创建的搜索帮助名称(如ZSRCH1),单击创建按钮。

步骤2:选择搜索帮助类型

系统提示创建搜索帮助类型,选择基本搜索帮助(默认值)。

步骤3:定义选择方法

在”选择方法”中指明数据源:

数据源类型 说明 示例
使用表作为数据源 ZCUSTOMERS1
视图 使用视图作为数据源 ZVIEW_TEST

步骤4:设置对话框类型

选择”对话框类型”,控制限制性对话框的外观:

选项 说明 用途
立即显示值 直接显示值列表 快速选择
带限制的对话框 先过滤再显示 精确查找
对话框用于输入帮助 手动输入后显示 辅助输入

步骤5:定义参数区域

为每个搜索帮助参数或字段,根据要求输入这些列字段。

参数字段详解

字段 描述 用途
搜索帮助参数 来自数据源的字段 参与搜索的字段
Import 复选框,是否为导入参数 参考搜索帮助
Export 复选框,是否为导出参数 将值传输到屏幕字段
LPos 选择列表中的物理位置 控制显示顺序
SPos 限制对话框中的物理位置 控制输入顺序
Data Element 数据元素名称 继承数据类型

示例参数设置

参数 Import Export LPos SPos 说明
CUSTOMER ☑️ ☑️ 1 1 客户编号参与搜索
NAME ☑️ ☑️ 2 2 客户姓名参与搜索

步骤6:测试和激活

一致性检查

按F8执行一致性检查,激活搜索帮助。

测试搜索帮助

系统显示”测试搜索帮助ZSRCH1”屏幕。

步骤7:功能验证

在”CUSTOMER”的”Ready for inp”屏幕字段中输入数字100004,按Enter键。

输出示例

系统显示客户编号100004和名称”STEPHEN”。

💡 搜索帮助的使用场景

1. 数据输入辅助

1
2
3
4
* 在屏幕中使用搜索帮助
PARAMETERS: p_carrid TYPE spfli-carrid
OBLIGATORY
MATCHCODE OBJECT ZCARRID.

2. 报表查询

1
2
3
4
* 在SELECT语句中使用
SELECT * FROM scustom
INTO TABLE lt_customers
WHERE id = '100004'.

3. 动态搜索

1
2
3
4
5
6
7
8
* 动态调用搜索帮助
CALL FUNCTION 'F4IF_INT_TABLE_VALUE_REQUEST'
EXPORTING
retfield = 'CARRID'
dynprofield = 'P_CARRID'
value_org = 'C'
TABLES
value_tab = lt_values.

🔧 参数设置详解

Import vs Export

类型 作用 场景
Import 将值传入搜索帮助 参考搜索帮助,基于输入值过滤
Export 将值从搜索帮助传出 将选择的值返回到屏幕字段

位置控制

位置字段 控制范围 示例
LPos 选择列表显示顺序 1表示第一个显示
SPos 限制对话框输入顺序 1表示第一个输入字段

⚠️ 注意事项

1. 数据源选择

  • 确保数据源表或视图已激活
  • 数据源字段需要有合适的搜索条件
  • 考虑数据量对性能的影响

2. 参数设置

  • 合理设置Import和Export标志
  • 正确配置LPos和SPos顺序
  • 确保Data Element匹配

3. 性能考虑

  • 避免过于复杂的搜索条件
  • 限制返回的记录数量
  • 考虑索引优化

🎯 最佳实践

1. 命名规范

  • 使用有意义的名称
  • 遵循Z或Y前缀规则
  • 名称清晰表达功能

2. 参数设计

  • 只包含必要的搜索字段
  • 设置合适的默认值
  • 提供足够的过滤选项

3. 用户体验

  • 显示有意义的标签
  • 提供清晰的排序
  • 支持模糊查询

ABAP 用户出口

🎯 用户出口概述

用户出口(User Exits)是SAP系统中用于==增强标准功能==的机制。当标准SAP提取器不提供期望的数据或所需的功能时(例如在授权或时间检查中),可以使用用户出口。

用户出口通常用于==销售和分销(SD)模块==。SAP在销售、运输和计费领域提供了许多用户出口。



graph TD
    A[SAP标准程序] --> B{是否满足需求?}
    B -->|是| C[直接使用]
    B -->|否| D[使用用户出口]
    D --> E[自定义修改]
    E --> F[增强后的功能]

访问用户出口

要访问每个销售区域中可用的退出,使用以下路径:

IMG → Sales and Distribution → System Modifications → User Exits

SD区域中每个出口的文档将在此详细解释。

💡查找销售凭证处理的用户出口
如果要在销售凭证处理(合同、报价或销售订单)中查找用户出口,请按照上述路径继续展开 Sales → User Exits。单击文档图标以查看销售凭证处理中提供的所有用户出口。

📋 主要用户出口类型

编号 用户出口名称 说明
1 USEREXIT_FIELD_MODIFICATION 用于修改屏幕属性
2 USEREXIT_SAVE_DOCUMENT 在用户点击保存时执行操作
3 USEREXIT_SAVE_DOCUMENT_PREPARE 检查输入字段、显示弹出式窗口并确认文档
4 USEREXIT_MOVE_FIELD_TO_VBAK 将用户标题更改移动到标题工作区
5 USEREXIT_MOVE_FIELD_TO_VBAP 将用户项目更改移动到SAP项目工作区

🔍 用户出口的特点

特征 说明
功能范围 功能与客户出口相同,但==仅适用于SD模块==
实现方式 退出实现为对==函数模块==的调用
本质 用户出口是对==SAP标准程序的修改==
⚠️注意
用户出口主要是对SAP标准程序的修改,修改时需要特别谨慎,确保不影响标准功能的正常运行。

💡 示例:查找用户出口

以下程序用于查找指定事务代码中的用户出口:

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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
REPORT zuserexit1.
TABLES:
tstc,
tstct,
tadir,
trdir,
tfdir,
enlfdir,
modsapt,
modact.
DATA:
jtab LIKE tadir OCCURS 0 WITH HEADER LINE,
field1(30),
v_devclass LIKE tadir-devclass.

PARAMETERS: p_tcode LIKE tstc-tcode OBLIGATORY.

SELECT SINGLE *
FROM tstc
WHERE tcode EQ p_tcode.
IF sy-subrc EQ 0.
SELECT SINGLE *
FROM tadir
WHERE pgmid = 'R3TR'
AND object = 'PROG'
AND obj_name = tstc-pgmna.
MOVE tadir-devclass TO v_devclass.

IF sy-subrc NE 0.
SELECT SINGLE *
FROM trdir
WHERE name = tstc-pgmna.
IF trdir-subc EQ 'F'.

SELECT SINGLE *
FROM tfdir
WHERE pname = tstc-pgmna.

SELECT SINGLE *
FROM enlfdir
WHERE funcname = tfdir-funcname.

SELECT SINGLE *
FROM tadir
WHERE pgmid = 'R3TR'
AND object = 'FUGR'
AND obj_name = enlfdir-area.
MOVE tadir-devclass TO v_devclass.
ENDIF.
ENDIF.

SELECT *
FROM tadir
INTO TABLE jtab
WHERE pgmid = 'R3TR'
AND object = 'SMOD'
AND devclass = v_devclass.

SELECT SINGLE *
FROM tstct
WHERE sprsl EQ sy-langu
AND tcode EQ p_tcode.
FORMAT COLOR COL_POSITIVE INTENSIFIED OFF.

WRITE:/(19) 'Transaction Code - ',
20(20) p_tcode,
45(50) tstct-ttext.
SKIP.

IF NOT jtab[] IS INITIAL.
WRITE:/(95) sy-uline.
FORMAT COLOR COL_HEADING INTENSIFIED ON.
WRITE:/1 sy-vline,
2 'Exit Name',
21 sy-vline ,
22 'Description',
95 sy-vline.
WRITE:/(95) sy-uline.
LOOP AT jtab.
SELECT SINGLE *
FROM modsapt
WHERE sprsl = sy-langu
AND name = jtab-obj_name.
FORMAT COLOR COL_NORMAL INTENSIFIED OFF.
WRITE:/1 sy-vline,
2 jtab-obj_name HOTSPOT ON,
21 sy-vline ,
22 modsapt-modtext,
95 sy-vline.
ENDLOOP.
WRITE:/(95) sy-uline.
DESCRIBE TABLE jtab.
SKIP.
FORMAT COLOR COL_TOTAL INTENSIFIED ON.
WRITE:/ 'No of Exits:' , sy-tfill.
ELSE.
FORMAT COLOR COL_NEGATIVE INTENSIFIED ON.
WRITE:/(95) 'User Exit doesn''t exist'.
ENDIF.
ELSE.
FORMAT COLOR COL_NEGATIVE INTENSIFIED ON.
WRITE:/(95) 'Transaction Code Does Not Exist'.
ENDIF.

AT LINE-SELECTION.
GET CURSOR FIELD field1.
CHECK field1(4) EQ 'JTAB'.
SET PARAMETER ID 'MON' FIELD sy-lisel+1(10).
CALL TRANSACTION 'SMOD' AND SKIP FIRST SCREEN.

执行示例

处理时,输入事务代码 ME01,然后按 F8(执行)按钮。

💥使用方法
  1. 运行程序,输入要查询的事务代码
  2. 按 F8 执行
  3. 程序会列出该事务代码关联的所有用户出口
  4. 点击出口名称可跳转到 SMOD 进行查看

🏗️ SAP增强技术对比



graph TD
    A[SAP增强技术] --> B[用户出口<br>User Exit]
    A --> C[客户出口<br>Customer Exit]
    A --> D[BADI<br>Business Add-In]
    B --> B1["仅SD模块<br>修改标准程序"]
    C --> C1["模块化增强<br>函数模块调用"]
    D --> D1["面向对象<br>可多次实现<br>支持过滤器"]

特征 用户出口 客户出口 BADI
适用范围 主要SD模块 所有模块 所有模块
实现方式 修改标准程序 函数模块调用 类的接口实现
可实现次数 一次 一次 多次
SAP版本 R/3 R/3 ECC 6.0+

⚠️ 注意事项

  1. 用户出口主要用于==SD模块==,但也可在其他模块中使用
  2. 修改用户出口需要==谨慎操作==,以确保不影响标准功能
  3. 建议在修改前==备份相关代码==
  4. 用户出口的修改需要==适当的授权==
  5. 新项目建议优先使用 ==BADI== 替代用户出口

ABAP 新语法

概述

SAP S/4HANA 引入了一系列 ABAP 新语法特性,使代码更加简洁、可读和现代化。本文档详细介绍这些新语法及其与传统语法的对比。

ℹ️版本要求
部分新语法功能需要 NetWeaver 7.40+7.54+ 版本支持。

2.1 Variable in-line declaration

内联变量声明,无需预先使用 DATA 语句声明变量。

1
2
3
4
5
6
" 新语法 (NW 7.40+)
DATA(variable) = 'A'.

" 传统语法
DATA variable TYPE string.
MOVE 'A' TO variable.
💡优势
  • 减少代码行数
  • 变量类型自动推断
  • 提高代码可读性

2.2 Use build-in function

使用内置函数替代语句操作。

1
2
3
4
5
6
" 新语法 - 使用内置函数
DATA(uppercase) = to_upper( lowercase ).

" 传统语法
DATA uppercase TYPE string.
TRANSLATE lowercase TO UPPER CASE.

常用内置函数

函数 说明 示例
to_upper() 转大写 to_upper( text )
to_lower() 转小写 to_lower( text )
strlen() 字符串长度 strlen( text )
substring() 截取子串 substring( val = text off = 1 len = 2 )

2.3 += operation

简化的复合赋值运算符(>= NW 7.54 支持)。

1
2
3
4
5
6
7
8
9
10
11
" 新语法 (>= NW 7.54)
index += 1.
count -= 1.
total *= 2.

" 传统语法
index = index + 1.
" 或
ADD 1 TO index.
SUBTRACT 1 FROM count.
MULTIPLY total BY 2.
⚠️版本注意
+=-=*=/= 等复合运算符需要 NetWeaver 7.54 或更高版本。

2.4 Key word NEW to instantiate object

使用 NEW 关键字实例化对象,替代 CREATE OBJECT

1
2
3
4
5
6
7
8
9
10
11
12
" 新语法
DATA(object) = NEW /clean/my_class( ).
DATA(lo_alv) = NEW cl_gui_alv_grid( i_parent = cl_gui_container=>default_screen ).

" 传统语法
DATA object TYPE REF TO /dirty/my_class.
CREATE OBJECT object TYPE /dirty/my_class.

DATA lo_alv TYPE REF TO cl_gui_alv_grid.
CREATE OBJECT lo_alv
EXPORTING
i_parent = cl_gui_container=>default_screen.

NEW 语法优势

  • 代码更简洁
  • 类型推断更准确
  • 支持链式调用
  • 支持内联声明

2.5 Key word FOR

使用 FOR 迭代表达式替代循环,实现函数式风格的表操作。

1
2
3
4
5
6
7
" 新语法 - FOR 表达式
result = VALUE #( FOR row IN input ( row-text ) ).

" 传统语法 - LOOP 循环
LOOP AT input INTO DATA(row).
INSERT row-text INTO TABLE result.
ENDLOOP.

更多示例

1
2
3
4
5
6
7
" 带条件的 FOR 表达式
result = VALUE #( FOR row IN input WHERE ( type = 'A' ) ( row-text ) ).

" 嵌套 FOR 表达式
result = VALUE #( FOR wa_1 IN tab1
FOR wa_2 IN tab2 WHERE ( key = wa_1-key )
( value = wa_1-val + wa_2-val ) ).
💥使用场景
  • 数据转换
  • 内表过滤
  • 批量数据处理
  • 生成测试数据

2.6 READ TABLE

使用表表达式(方括号)替代 READ TABLE 语句。

1
2
3
4
5
6
7
8
" 新语法 - 表表达式
DATA(line) = value_pairs[ name = 'A' ].

" 传统语法
READ TABLE value_pairs INTO DATA(line) WITH KEY name = 'A'.
IF sy-subrc = 0.
" 处理数据
ENDIF.

处理不存在的行

1
2
3
4
5
6
7
" 使用 OPTIONAL 避免异常
DATA(line) = VALUE #( value_pairs[ name = 'A' ] OPTIONAL ).

" 或使用 line_exists 检查
IF line_exists( value_pairs[ name = 'A' ] ).
DATA(line) = value_pairs[ name = 'A' ].
ENDIF.

读取指定行

1
2
3
4
5
" 读取第5行
DATA(line) = internal_table[ 5 ].

" 使用默认值
DATA(line) = VALUE #( internal_table[ key = 'X' ] OPTIONAL ).

2.7 xsdbool to do boolean expression

使用 xsdbool 进行布尔表达式求值,简化条件判断。

1
2
3
4
5
6
7
8
9
10
11
12
" 新语法
DATA(exists) = xsdbool( line_exists( value_pairs[ name = 'A' ] ) ).
IF line_exists( value_pairs[ name = 'A' ] ).
" 处理存在的情况
ENDIF.

" 传统语法
READ TABLE value_pairs TRANSPORTING NO FIELDS WITH KEY name = 'A'.
DATA(exists) = xsdbool( sy-subrc = 0 ).
IF exists = abap_true.
" 处理存在的情况
ENDIF.

xsdbool 返回值

条件 返回值
'X' (abap_true)
'' (abap_false)

使用场景

1
2
3
4
5
6
7
8
" 在 SQL WHERE 条件中使用
SELECT * FROM mara
INTO TABLE @lt_mara
WHERE xsdbool( matnr IS NOT NULL ) = @abap_true.

" 在条件逻辑中使用
DATA: flag TYPE abap_bool.
flag = xsdbool( value > 0 ).

2.8 select BKPF-AWKEY

使用新语法进行 SQL 查询,包括 concat 函数和 @ 转义符处理复合字段。

场景说明

BKPF-AWKEY 是财务凭证表中的复合关键字段,由 BUKRS + BELNR + GJAHR 组成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
TYPES: BEGIN OF ts_doc_key,
bukrs TYPE bkpf-bukrs,
belnr TYPE bkpf-belnr,
gjahr TYPE bkpf-gjahr,
END OF ts_doc_key,
BEGIN OF ts_doc_header,
bukrs TYPE bkpf-bukrs,
belnr TYPE bkpf-belnr,
gjahr TYPE bkpf-gjahr,
awtyp TYPE bkpf-awtyp,
awsys TYPE bkpf-awsys,
awkey TYPE bkpf-awkey,
END OF ts_doc_header.

DATA: lt_doc_key TYPE SORTED TABLE OF ts_doc_key
WITH UNIQUE KEY bukrs belnr gjahr.
DATA: lt_header1 TYPE STANDARD TABLE OF ts_doc_header
WITH EMPTY KEY,
lt_header2 TYPE STANDARD TABLE OF ts_doc_header
WITH EMPTY KEY.

方法1: 使用 concat 连接复合键

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
" 准备10个凭证键
SELECT bukrs,
belnr,
gjahr
FROM bkpf
INTO TABLE @lt_doc_key
UP TO 10 ROWS
WHERE awtyp = 'BKPF'.

" 使用 concat 连接字段匹配 AWKEY
SELECT a~bukrs,
a~belnr,
a~gjahr,
a~awtyp,
a~awsys,
a~awkey
FROM bkpf AS a INNER JOIN @lt_doc_key AS b
ON a~awkey = concat( concat( b~belnr, b~bukrs ), b~gjahr )
WHERE a~awtyp = 'BKPF'
INTO TABLE @lt_header1.

方法2: 分别匹配字段(推荐)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
" 分别匹配各字段,性能更优
SELECT a~bukrs,
a~belnr,
a~gjahr,
a~awtyp,
a~awsys,
a~awkey
FROM bkpf AS a INNER JOIN @lt_doc_key AS b
ON a~bukrs = b~bukrs
AND a~belnr = b~belnr
AND a~gjahr = b~gjahr
WHERE a~awtyp = 'BKPF'
INTO TABLE @lt_header2.

WRITE: 'Done'.
💡性能对比
方法 优点 缺点
concat 代码简洁 可能无法使用索引,性能较差
分别匹配 可以利用数据库索引,性能更优 代码稍长

推荐:在性能敏感场景优先使用分别匹配的方法。


总结

ABAP 新语法是 SAP S/4HANA 平台的重要改进,主要包括:

特性 语法 优势
内联声明 DATA(var) = value 简化变量声明
内置函数 to_upper() 代码更简洁
复合运算符 index += 1 减少冗余代码
NEW 关键字 NEW class( ) 替代 CREATE OBJECT
FOR 迭代 FOR ... IN ... 函数式风格
表表达式 table[ key ] 替代 READ TABLE
布尔表达式 xsdbool() 简化条件判断
SQL 增强 concat(), @ 更强大的查询能力
💡最佳实践
  1. 在新项目中优先使用新语法
  2. 重构现有代码时逐步采用新语法
  3. 注意版本兼容性要求
  4. 性能敏感场景需充分测试

ABAP 表

📊 表的基本概念

表的定义

表可以独立于ABAP字典中的数据库来定义。当在ABAP词典中激活表时,也在数据库中创建其字段的类似副本。

自动转换

在ABAP字典中定义的表自动转换为与数据库兼容的格式,因为表的定义取决于SAP系统使用的数据库。

表的结构

表由一个或多个字段组成,每个字段都使用其数据类型和长度进行定义。存储在表中的大量数据分布在表中定义的几个字段中。

🔧 表字段的类型

字段元素详解

编号 元素 说明 示例
1 字段名称 给字段赋予的名称,最多16个字符 CLIENT, NAME
2 键标志 确定字段是否属于关键字字段 ☑️ 主键字段
3 字段类型 为字段分配数据类型 CHAR, NUMC, DEC
4 字段长度 可在字段中输入的字符数 10, 20, 40
5 小数位数 定义小数点后允许的位数 2, 3
6 短文本 描述相应字段的含义 “客户ID”

字段命名规则

  • 长度限制:最多16个字符
  • 字符组成:数字、字母和下划线
  • 起始字符:必须以字母开头

🛠️ 在ABAP字典中创建表

步骤1:启动事务

使用事务 SE11,选择”数据库表”单选按钮,输入要创建的表名称(如ZCUSTOMERS1),点击创建按钮。

步骤2:基本信息设置

交货和维护选项卡

  1. 在简短说明字段中输入描述性短文本
  2. 点击交货类别字段旁边的搜索帮助图标
  3. 选择 “A [应用程序表(主数据和事务数据)]” 选项
  4. 从”数据浏览器/表视图维护”下拉菜单中选择”允许显示/维护”选项

步骤3:字段定义

选择字段选项卡,开始定义表字段:



graph TD
    A[表设计] --> B[字段定义]
    B --> C[字段名称]
    B --> D[数据元素]
    B --> E[键标志]
    
    C --> C1[CLIENT]
    C --> C2[CUSTOMER]
    C --> C3[NAME]
    C --> C4[TITLE]
    C --> C5[DOB]

步骤4:创建关键字段

CLIENT字段

  • 字段名称:CLIENT
  • 数据元素:MANDT
  • 设置:选中”键”框
  • 系统自动填写:数据类型、长度、小数和简短描述

CUSTOMER字段

  • 字段名称:CUSTOMER
  • 数据元素:ZCUSTNUM(需要创建)
  • 设置:选中”键”框

步骤5:创建数据元素

当数据元素不存在时,系统会提示创建:

  1. 双击新的数据元素
  2. 在”创建数据元素”窗口中回答”是”
  3. 在简短说明区域输入”客户编号”

步骤6:域的创建

基本数据类型定义

  1. 在”定义”选项卡中,点击数据类型框
  2. 从下拉菜单中选择 “NUMC” 类型
  3. 在”编号”中输入数字8
  4. 在”小数位数”区域中输入0
  5. 选择输出长度为8,按Enter键

字段标签创建

创建四个字段标签:

  • 短文本
  • 中等文本
  • 长文本
  • 标题

步骤7:保存和激活

  1. 点击保存按钮
  2. 激活对象
  3. 按F3返回维护/更改数据元素屏幕
  4. 保存并激活元素

步骤8:技术设置

从工具栏中选择”技术设置”:

设置项 选项 说明
数据类 APPL0 应用程序表
大小类别 0 第一个大小类别
缓冲选项 不允许缓冲 避免数据不一致

步骤9:最终激活

  1. 点击保存
  2. 回到表格并激活它
  3. 系统显示”表’ZCUSTOMERS1’被激活”

📝 表的创建流程总结

创建步骤图解



graph TB
    A[SE11事务] --> B[选择数据库表]
    B --> C[输入表名]
    C --> D[定义字段]
    D --> E[设置数据元素]
    E --> F[创建域(如果需要)]
    F --> G[设置技术属性]
    G --> H[保存激活]

字段创建注意事项

  1. 数据元素:每个字段必须具有数据元素
  2. 属性继承:字段从数据元素获取数据类型、长度、小数位数和短文本
  3. 键设置:关键字段必须选中”键”框
  4. 命名规范:字段名称必须以字母开头,最多16个字符

💡 表的使用场景

1. 数据存储

1
2
3
* 使用表存储数据
TABLES: scustom.
DATA: customer TYPE scustom.

2. 数据操作

1
2
3
* 读取表数据
SELECT * FROM scustom INTO TABLE lt_customers
WHERE carrid = 'LH'.

3. 表维护

1
2
3
4
5
* 维护表数据
CALL FUNCTION 'VIEW_MAINTENANCE_CALL'
EXPORTING
action = 'A'
view_name = 'ZVIEW'.

⚠️ 重要注意事项

1. 激活顺序

  • 先激活域
  • 再激活数据元素
  • 最后激活表

2. 性能考虑

  • 合理设置缓冲选项
  • 选择合适的数据类
  • 考虑表的大小类别

3. 数据完整性

  • 定义合适的主键
  • 设置正确的数据类型
  • 使用适当的约束

ABAP 模块化

🎯 模块化概述

定义

模块化是将大型和复杂的任务分解成更小和更简单的任务,每个任务放置在其单独的模块中。

模块化的优势



graph TD
    A[模块化编程] --> B[代码重用]
    A --> C[减少冗余]
    A --> D[提高可读性]
    A --> E[易于维护]
    A --> F[专注开发]

模块化的好处

  1. 开发效率:开发者可以集中精力,不会分心
  2. 代码重用:相同代码可以被多次使用
  3. 可维护性:即使是在后续维护周期中也能保持良好的可读性
  4. 调试便利:小的、模块化的代码段易于调试

🔧 ABAP中的模块化类型

1. 源代码级别的模块化

Local Macros(本地宏)

  • 在程序内部定义的宏
  • 用于重复使用的代码片段
  • 使用 DEFINEEND-OF-DEFINITION 定义
1
2
3
4
5
DEFINE write_header.
WRITE: / '---------------------------------'.
WRITE: / &1.
WRITE: / '---------------------------------'.
END-OF-DEFINITION.

Global Include Programs(全局包含程序)

  • 独立的程序文件
  • 可以被多个程序包含
  • 使用 INCLUDE 语句
1
INCLUDE Z_GLOBAL_UTILS.  " 包含全局工具程序

2. 处理块级别的模块化

处理块的分类

类型 调用方式 说明
事件块 从程序外部和ABAP运行时环境调用 如 START-OF-SELECTION
对话模块 从程序外部调用 如 PBO、PAI 模块
子程序 从ABAP程序内部调用 使用 PERFORM 语句
功能模块 从ABAP程序内部调用 使用 CALL FUNCTION 语句

📝 模块化源代码

源代码模块化概念

模块化源代码意味着在模块中放置一个ABAP语句序列,可以根据用户的需求在程序中调用。

源代码模块的好处

  • 增强可读性:使ABAP程序更易于理解和维护
  • 避免重复:防止不断重复写入相同的语句
  • 易于理解:使代码对于第一次阅读的人来说更容易理解

💡 模块化编程示例

1. 本地宏示例

1
2
3
4
5
6
7
8
* 定义格式化宏
DEFINE format_number.
WRITE: / &1 USING EDIT MASK '__.__.__,--'.
END-OF-DEFINITION.

* 使用宏
format_number '1234567.89'.
format_number '9876543.21'.

2. 包含程序示例

1
2
3
4
5
6
7
8
9
10
11
* 主程序
REPORT z_main_program.
INCLUDE Z_GLOBAL_CONSTANTS. " 包含常量定义
INCLUDE Z_GLOBAL_FUNCTIONS. " 包含函数定义

START-OF-SELECTION.
DATA: lv_result TYPE i.

* 使用包含的函数
lv_result = calculate_discount( 1000, 10 ).
WRITE: / 'Discount Amount:', lv_result.

3. 子程序示例

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

* 定义子程序
FORM calculate_tax USING iv_amount TYPE p
CHANGING cv_tax_amount TYPE p.
DATA: lv_rate TYPE p VALUE 0.10.

cv_tax_amount = iv_amount * lv_rate.
ENDFORM.

* 调用子程序
DATA: lv_amount TYPE p VALUE 1000,
lv_tax TYPE p.

PERFORM calculate_tax USING lv_amount
CHANGING lv_tax.

WRITE: / 'Tax Amount:', lv_tax.

🎯 模块化最佳实践

1. 模块设计原则



graph LR
    A[模块设计原则] --> B[单一职责]
    A --> C[高内聚低耦合]
    A --> D[接口清晰]
    A --> E[功能独立]

2. 命名规范

模块类型 命名规则 示例
以Z开头,描述功能 Z_FORMAT_DATE
包含程序 Z_GLOBAL_前缀 Z_GLOBAL_UTILS
子程序 动词+名词 CALCULATE_TOTAL
功能模块 模块组名+功能 Z_DEMO_CALCULATE

3. 代码组织建议

  1. 按功能分组:将相关功能的代码放在一起
  2. 避免重复:提取公共代码到独立模块
  3. 文档化:为每个模块添加清晰的注释
  4. 测试独立:确保每个模块可以独立测试

⚠️ 注意事项

1. 模块大小

  • 保持模块适当大小
  • 避免过大或过小的模块
  • 确保模块功能单一

2. 依赖管理

  • 减少模块间的依赖
  • 明确模块接口
  • 避免循环依赖

3. 性能考虑

  • 避免过度模块化
  • 平衡代码复用和性能
  • 考虑调用开销

ABAP 类

🏗️ 类概述

定义

类用于指定对象的形式,它将数据表示和将该数据操作为一个整齐包的方法组合。类中的数据和函数称为的成员。

类的作用

类定义时,可以为数据类型定义蓝图:

  • 不定义具体数据,而是定义类名称的意义
  • 定义对象将包括什么
  • 定义可以对对象执行的操作
  • 定义对象的抽象特性(属性、字段和方法)

📝 类定义和实现

类定义语法

1
2
3
4
CLASS <class_name> DEFINITION.
..........
..........
ENDCLASS.

类实现语法

1
2
3
4
CLASS <class_name> IMPLEMENTATION.
...........
..........
ENDCLASS.

类的结构



graph TD
    A[类定义] --> B[属性]
    A --> C[方法]
    A --> D[事件]
    A --> E[类型]
    A --> F[常量]
    
    C --> C1[实例方法]
    C --> C2[静态方法]

注意事项

  • 类的实现包含其所有方法的实现
  • 在类声明中声明方法,在类实现部分实现
  • ABAP对象中,类的结构包含属性、方法、事件、类型和常量等组件

🏷️ 属性

属性概述

属性是可以具有任何数据类型(如C、I、F和N)的类的数据字段。它们在类声明中声明。

属性类型

1. 实例属性

1
2
* 定义实例属性
DATA: mv_text TYPE char20 VALUE 'Instance Data'.

特点

  • 定义对象的实例特定状态
  • 不同对象的状态不同
  • 使用DATA语句声明

2. 静态属性

1
2
* 定义静态属性
CLASS-DATA: mv_shared_text TYPE char20 VALUE 'Shared Data'.

特点

  • 定义由类的所有实例共享的类的公共状态
  • 更改一个对象的静态属性,对类的所有其他对象也可见
  • 使用CLASS-DATA语句声明

🔧 方法

方法概述

方法是表示类中的对象的行为的函数或过程。类的方法可以访问类的任何属性。

方法实现语法

1
2
3
4
METHOD <m_name>.
..........
..........
ENDMETHOD.

方法调用

可以使用CALL METHOD语句调用方法:

1
CALL METHOD: object->method_name.

方法参数

1
2
3
4
5
6
* 方法定义包含参数
METHODS:
calculate_discount
IMPORTING iv_amount TYPE p
EXPORTING ev_discount TYPE p
CHANGING cv_total TYPE p.

👁️ 访问控制

可见性部分

类组件可以在控制如何访问这些组件的公共、私有或受保护的可见性部分中定义。

1. Public(公共部分)

1
2
3
PUBLIC SECTION.
DATA: mv_public_data TYPE string.
METHODS: public_method.

特点

  • 可以从任何上下文访问
  • 可以由该类和程序的任何其他类、子类访问
  • 默认情况下,所有成员都是私有的

2. Protected(受保护部分)

1
2
3
PROTECTED SECTION.
DATA: mv_protected_data TYPE string.
METHODS: protected_method.

特点

  • 只能由类和子类(派生类)访问

3. Private(私有部分)

1
2
3
PRIVATE SECTION.
DATA: mv_private_data TYPE string.
METHODS: private_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
27
REPORT zaccess1.

CLASS class1 DEFINITION.
PUBLIC SECTION.
DATA: text1 TYPE char25 VALUE 'Public Data'.
METHODS meth1.
PROTECTED SECTION.
DATA: text2 TYPE char25 VALUE 'Protected Data'.
PRIVATE SECTION.
DATA: text3 TYPE char25 VALUE 'Private Data'.
ENDCLASS.

CLASS class1 IMPLEMENTATION.
METHOD meth1.
WRITE: / 'Public Method:',
/ text1,
/ text2,
/ text3.
SKIP.
ENDMETHOD.
ENDCLASS.

START-OF-SELECTION.
DATA: objectx TYPE REF TO class1.
CREATE OBJECT: objectx.
CALL METHOD: objectx->meth1.
WRITE: / objectx->text1.

输出示例

1
2
3
4
5
6
Public Method:
Public Data
Protected Data
Private Data

Public Data

🔒 静态属性

静态属性定义

静态属性用语句CLASS-DATA声明。所有对象或实例都可以使用类的静态属性。

访问方式

静态属性直接在类名的帮助下访问:

1
class_name=>name_1 = 'Some Text'

静态属性示例

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

CLASS class1 Definition.
PUBLIC Section.
CLASS-DATA: name1 TYPE char45,
data1 TYPE i.
Methods: meth1.
ENDCLASS.

CLASS class1 Implementation.
Method meth1.
Do 4 Times.
data1 = 1 + data1.
Write: / data1, name1.
EndDo.
Skip.
EndMethod.
ENDCLASS.

START-OF-Selection.
class1=>name1 = 'ABAP Object Oriented Programming'.
class1=>data1 = 0.

Data: Object1 TYPE REF TO class1,
Object2 TYPE REF TO class1.

Create Object: Object1, Object2.

CALL Method: Object1->meth1,
Object2->meth1.

输出结果

Static Attributes

⚙️ 构造函数

构造函数概述

构造函数是在创建对象或访问类的组件时自动调用的特殊方法。

特点

  • 每当创建对象时,构造器被触发
  • 需要调用方法来触发一般方法
  • 用于初始化对象的状态

构造函数示例

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

CLASS class1 Definition.
PUBLIC Section.
Methods: method1, constructor.
ENDCLASS.

CLASS class1 Implementation.
Method method1.
Write: / 'This is Method1'.
EndMethod.

Method constructor.
Write: / 'Constructor Triggered'.
EndMethod.
ENDCLASS.

START-OF-Selection.
Data Object1 TYPE REF TO class1.
Create Object Object1.

输出结果

1
Constructor Triggered

🔄 ME运算符

ME运算符概述

当在类的public节中声明任何类型的变量时,可以在任何其他实现中使用它。

工作原理

  • 变量可以在public section中用初始值声明
  • 可以在具有不同值的方法中再次声明该变量
  • 使用”ME”运算符可以获取类级别的变量值
  • 直接声明获得方法级别的变量值

ME运算符示例

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

CLASS class1 Definition.
PUBLIC Section.
Data text1 TYPE char25 Value 'This is CLASS Attribute'.
Methods method1.
ENDCLASS.

CLASS class1 Implementation.
Method method1.
Data text1 TYPE char25 Value 'This is METHOD Attribute'.
Write: / ME->text1,
/ text1.
EndMethod.
ENDCLASS.

START-OF-Selection.
Data objectx TYPE REF TO class1.
Create Object objectx.
CALL Method objectx->method1.

输出结果

1
2
This is CLASS Attribute
This is METHOD Attribute

ME运算符的使用

1
2
3
4
5
6
7
8
" 使用ME访问类属性
METHOD my_method.
" ME-> 确保访问的是类的属性,而不是局部变量
ME->mv_attribute = 'New Value'.

" 如果没有ME,可能会访问局部变量
mv_attribute = 'Local Value'.
ENDMETHOD.

💡 类的完整示例

完整类定义

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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
REPORT z_class_example.

* 类定义
CLASS zcl_car DEFINITION.
PUBLIC SECTION.
" 公共属性
DATA:
make TYPE string,
model TYPE string,
speed TYPE i,
max_speed TYPE i VALUE 200.

" 公共方法
METHODS:
constructor
IMPORTING iv_make TYPE string
iv_model TYPE string,
accelerate
IMPORTING iv_amount TYPE i
RAISING cx_speed_limit,
brake
IMPORTING iv_amount TYPE i,
get_speed
RETURNING(value(rv_speed)) TYPE i.

PROTECTED SECTION.
" 受保护属性
DATA:
engine_on TYPE abap_bool,
fuel TYPE p LENGTH 16 DECIMALS 2 VALUE 50.0.

PRIVATE SECTION.
" 私有方法
METHODS:
check_fuel
RAISING cx_insufficient_fuel,
update_fuel
IMPORTING iv_consumption TYPE p.
ENDCLASS.

* 类实现
CLASS zcl_car IMPLEMENTATION.
METHOD constructor.
me->make = iv_make.
me->model = iv_model.
me->speed = 0.
me->engine_on = abap_true.
WRITE: / 'Created:', me->make, me->model.
ENDMETHOD.

METHOD accelerate.
me->check_fuel( ).

IF iv_amount > 0.
me->speed = me->speed + iv_amount.

IF me->speed > me->max_speed.
me->speed = me->max_speed.
RAISE EXCEPTION TYPE cx_speed_limit.
ENDIF.

me->update_fuel( iv_amount / 10 ).
ENDIF.
ENDMETHOD.

METHOD brake.
IF iv_amount > 0.
me->speed = me->speed - iv_amount.
IF me->speed < 0.
me->speed = 0.
ENDIF.
ENDIF.
ENDMETHOD.

METHOD get_speed.
rv_speed = me->speed.
ENDMETHOD.

METHOD check_fuel.
IF me->fuel < 10.0.
RAISE EXCEPTION TYPE cx_insufficient_fuel.
ENDIF.
ENDMETHOD.

METHOD update_fuel.
me->fuel = me->fuel - iv_consumption.
IF me->fuel < 0.
me->fuel = 0.
ENDIF.
ENDMETHOD.
ENDCLASS.

* 使用示例
START-OF-SELECTION.
DATA: lo_car TYPE REF TO zcl_car.

CREATE OBJECT lo_car
EXPORTING
iv_make = 'BMW'
iv_model = 'X5'.

lo_car->accelerate( 50 ).
WRITE: / 'Current Speed:', lo_car->get_speed( ).

lo_car->brake( 20 ).
WRITE: / 'After Braking:', lo_car->get_speed( ).

⚠️ 注意事项

1. 访问控制

  • 合理使用PUBLIC、PROTECTED、PRIVATE部分
  • 数据通常放在PRIVATE部分
  • 方法放在PUBLIC部分以便外部调用

2. 性能考虑

  • 避免过度使用静态属性
  • 合理使用构造函数进行初始化
  • 注意ME运算符的使用场景

3. 错误处理

  • 使用异常类处理错误
  • 在构造函数中进行必要验证
  • 提供清晰的错误信息

ABAP 类接口

📖 简介

接口(Interface)在ABAP中类似于类,充当对象的数据类型。接口的组件与类的组件相同,但接口的定义不需要可见性部分。

🔄 接口的核心概念

  • 数据类型:接口作为对象的数据类型
  • 契约定义:定义类必须实现的方法
  • 多态基础:与继承一起实现多态性
  • 松耦合:降低类之间的耦合度

💡 关键区别 - 与类的声明不同,接口的声明不包括可见性部分。这是因为在接口声明中定义的组件总是集成在类的public visibility部分。

🏗️ 接口语法

基本格式

1
2
3
4
5
6
INTERFACE <intf_name>.
DATA.....
CLASS-DATA.....
METHODS.....
CLASS-METHODS.....
ENDINTERFACE.

在此语法中:

  • <intf_name> 表示接口的名称
  • DATACLASS-DATA 分别用于定义接口的实例和静态属性
  • METHODSCLASS-METHODS 分别用于定义接口的实例和静态方法

📊 接口组件类型

组件类型 关键字 描述
实例数据 DATA 接口实例的数据属性
静态数据 CLASS-DATA 接口类的静态属性
实例方法 METHODS 接口实例的方法
静态方法 CLASS-METHODS 接口类的静态方法

🔗 接口实现

在类中使用接口

1
INTERFACE <intf_name>.

注意:此语法必须在类的public部分中使用。

实现接口方法

1
2
3
METHOD <intf_name~method_m>.
<statements>.
ENDMETHOD.

在此语法中:

  • <intf_name~method_m> 表示 <intf_name> 接口的方法的完全声明的名称

🚨 重要特性

接口的所有方法都是抽象的

  • 它们是完全声明的,包括它们的参数接口
  • 但是没有在接口中实现
  • 所有想要使用接口的类都必须实现接口的所有方法
  • 否则,该类将成为抽象类

📝 接口示例

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

INTERFACE my_interface1.
METHODS msg.
ENDINTERFACE.

CLASS num_counter DEFINITION.
PUBLIC SECTION.
INTERFACES my_interface1.
METHODS add_number.
PRIVATE SECTION.
DATA num TYPE i.
ENDCLASS.

CLASS num_counter IMPLEMENTATION.
METHOD my_interface1~msg.
WRITE: / 'The number is', num.
ENDMETHOD.
METHOD add_number.
ADD 7 TO num.
ENDMETHOD.
ENDCLASS.

CLASS drive1 DEFINITION.
PUBLIC SECTION.
INTERFACES my_interface1.
METHODS speed1.
PRIVATE SECTION.
DATA wheel1 TYPE i.
ENDCLASS.

CLASS drive1 IMPLEMENTATION.
METHOD my_interface1~msg.
WRITE: / 'Total number of wheels is', wheel1.
ENDMETHOD.
METHOD speed1.
ADD 4 TO wheel1.
ENDMETHOD.
ENDCLASS.

START-OF-SELECTION.
DATA object1 TYPE REF TO num_counter.
CREATE OBJECT object1.
CALL METHOD object1->add_number.
CALL METHOD object1->my_interface1~msg.

DATA object2 TYPE REF TO drive1.
CREATE OBJECT object2.
CALL METHOD object2->speed1.
CALL METHOD object2->my_interface1~msg.

📝 输出结果

1
2
The number is 7
Total number of wheels is 4

🔍 代码解析



graph TB
    A[接口 my_interface1] -->|实现| B[num_counter]
    A -->|实现| C[drive1]
    B --> D[add_number方法]
    B --> E[msg实现]
    C --> F[speed1方法]
    C --> G[msg实现]

在上面的示例中:

  • my_interface1 是包含 msg 方法的接口
  • 定义了两个类:num_counterdrive1
  • 这两个类都实现了 msg 方法,但实现内容不同
  • 每个类还有自己的特定方法:add_numberspeed1

💡 注意 - add_numberspeed1 方法特定于各个类,而 msg 方法通过接口统一调用,但根据类不同有不同实现。

💡 接口的优势

📈 1. 实现多态性

当两个类似的类具有相同名称的方法但功能彼此不同时,使用接口。接口以及继承特性为多态性提供了基础。这是因为接口中定义的方法在不同类中的行为可能不同。

🔧 2. 降低耦合度



graph LR
    A[调用方] --> B[接口]
    B --> C[实现类1]
    B --> D[实现类2]
    B --> E[实现类3]

调用方只需要知道接口,而不需要知道具体的实现类。

🛡️ 3. 标准化契约

接口定义了统一的契约,确保所有实现类都遵循相同的规范。

🎯 使用场景

1. 策略模式实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
INTERFACE strategy.
METHODS: execute IMPORTING data TYPE any.
ENDINTERFACE.

class_algo_a DEFINITION.
PUBLIC SECTION.
INTERFACES strategy.
PRIVATE SECTION.
METHODS: algo_a_specific IMPORTING data TYPE any.
ENDCLASS.

class_algo_b DEFINITION.
PUBLIC SECTION.
INTERFACES strategy.
PRIVATE SECTION.
METHODS: algo_b_specific IMPORTING data TYPE any.
ENDCLASS.

2. 工厂模式支持

1
2
3
4
5
6
7
8
9
10
INTERFACE factory.
CLASS-METHODS: create_object RETURNING VALUE(obj) TYPE ref to any.
ENDINTERFACE.

class_product_factory DEFINITION.
PUBLIC SECTION.
INTERFACES factory.
PRIVATE SECTION.
METHODS: create_product RETURNING VALUE(obj) TYPE ref to any.
ENDCLASS.

3. 事件处理

1
2
3
4
5
6
7
8
9
10
11
12
INTERFACE event_handler.
METHODS: handle_event IMPORTING event_type TYPE string
data TYPE any.
ENDINTERFACE.

class_ui_controller DEFINITION.
PUBLIC SECTION.
INTERFACES event_handler.
PRIVATE SECTION.
METHODS: process_event IMPORTING event_type TYPE string
data TYPE any.
ENDCLASS.

📋 接口设计原则

最佳实践

  1. 单一职责原则

    • 每个接口应该只定义一个相关的功能集
    • 避免接口过于庞大
  2. 接口隔离原则

    • 客户端不应该依赖它不需要的接口
    • 将大接口拆分为多个小接口
  3. 开闭原则

    • 对扩展开放
    • 对修改封闭

📊 接口设计检查清单

设计原则 实践方式
职责单一 每个接口专注于特定功能领域
保持简洁 避免接口过于庞大复杂
命名清晰 使用有意义的名称描述接口目的
版本管理 当需要修改接口时考虑向后兼容性

🔧 高级特性

1. 接口继承

1
2
3
4
5
6
7
INTERFACE parent_interface.
METHODS: parent_method.
ENDINTERFACE.

INTERFACE child_interface INHERITING FROM parent_interface.
METHODS: child_method.
ENDINTERFACE.

2. 接口常量

1
2
3
4
INTERFACE constants_interface.
CONSTANTS: max_items TYPE i VALUE 100,
min_items TYPE i VALUE 0.
ENDINTERFACE.

3. 接口异常

1
2
3
4
5
6
INTERFACE exception_interface.
METHODS: method_name
IMPORTING param TYPE any
EXCEPTIONS: error1,
error2.
ENDINTERFACE.
0%