使用封装确保一致性

学习目标

  • 介绍封装。

  • 定义和使用构造函数。

数据封装

在面向对象的编程中,对象对应于实际对象,例如员工、车辆或转机。它具有描述它的属性,在转机的情况下,这些属性是承运商标识和航班号。

现在,让我们考虑当使用此对象的程序要为这些属性提供值时会发生什么。很明显,仅应接受与现实世界中的航班连接相对应的那些承运商标识和航班号组合。

在对象方向上,客户端程序不能直接更改属性值。相反,它需要调用方法来执行此任务。方法随对象一起提供,随后可以检查运输公司标识和航班号的组合是否有效,如果不是这种情况,则拒绝更改。

作为类 lcl_connection 的开发人员,我们可以通过将属性 carrier_id 和 connection_id 设置为私有或至少为只读来确保使用方法 set_attributes()。

此概念称为数据封装;有关转机的信息由连接对象本身管理,不能由任何其他人操作。这可确保无法绕过一致性检查 - 无论是故意还是不知情。这是面向对象的主要优势之一。

在面向对象的编程中,建议尽可能地使用数据封装!

使用我们现在定义的公共属性,可以在类内部和外部的任何位置读取和更改属性值。

要限制对属性的访问,您有两个选项:

  1. 在公共部分中保留 DATA 语句或 CLASS-DATA 语句并添加附加项 READ-ONLY。执行此操作时,禁止在类外部对 属性进行写入访问,但仍可以进行读取访问。

注意

只允许在类的公共部分中使用附加项 READ-ONLY。

  1. 将 DATA 语句或 CLASS-DATA 语句从公共部分移动到其他可见性部分之一,例如私有部分。在执行此操作时,禁止在 类之外对 属性进行读取访问和写入访问。

提示

ADT 提供快速修复以更改属性的可见性。要将光标置于属性名称中,请按 Ctrl + 1,然后选择将 <属性> 设为私有。

如何使用私有属性

演示观看演示(SAP 官方交互式练习)

实例构造函数

通过将属性设置为私有或只读(至少),可确保客户端程序使用可用的 set_attributes() 方法设置属性 carrier_id 和 connection_id 的值。

但仍可能存在不一致:

  • 不能强制程序为每个新实例调用 set_attributes()。因此,可能存在 carrier_id 和 connection_id 初始值的实例。

  • 客户端程序可以为同一实例多次调用 set_attributes()。这也不应如此。

您需要的是一种在实例化期间强制使用非初始值并防止后续更改的技术。

要解决这些问题,面向对象的编程语言使用构造方法。

当您创建类的新实例时,运行时系统会自动调用构造函数,但您可能不能显式调用它。因此,可确保构造函数仅为您创建的每个实例运行一次。

从语法角度来看,构造方法具有以下属性:

  • 具有保留名称构造函数的公共实例方法

  • 可能具有导入参数,例如用于获取新实例属性的起始值

  • 可能会引发异常

注意

在 ABAP 中,无法在同一类中定义多个构造器方法。

提示

在 ADT 中,您可以使用快速修复生成构造器方法。要使用此快速修复,请执行以下操作:

  1. 在定义或实施部分中,将光标置于类名称中,然后按 Ctrl + 1。2. 从可用快速修复列表中,选择生成构造函数 3. 在出现的对话框窗口中,选择要使用构造函数进行初始化的属性,然后选择完成生成构造函数后,可以根据需要调整其定义和实施。

此示例显示类 lcl_connection 的构造函数。生成的定义包含每个属性 carrier_idconnection_id 的导入参数。

实施的生成部分包含每个属性的值分配,右侧有相关的导入参数。

此示例显示类 lcl_connection 的已生成构造函数的一些手动添加。

为了拒绝使用初始值创建实例,将一致性检查添加到实施中,并将 RAISING 子句添加到构造函数定义。

由于构造函数只针对类 lcl_connection 的每个新实例执行一次,因此构造函数实现是增加静态属性 conn_counter 的完美位置。

实例化具有构造函数的类时,系统会自动调用构造函数方法。如果构造函数具有导入参数,则在 NEW 表达式中传递它们的方式与将参数传递到常规方法完全相同。

注意

构造函数只能具有导入参数。NEW 表达式中既不需要也不允许关键字 EXPORTING。

如果构造函数具有异常,则必须确保通过将实例化括在 TRY… CATCH...ENDTRY 块中来捕获它们。如果构造函数引发异常,控制立即返回到包含 NEW 表达式的程序。在这种情况下,您不会收到 类的新实例。

如何定义实例构造函数

演示观看演示(SAP 官方交互式练习)

静态构造函数

虽然在创建类的每个实例时调用一次实例构造函数,但有时只需对整个类执行一次操作。为此,ABAP 允许您定义静态构造函数,也称为类构造函数。

仅当在程序执行期间首次寻址类时,运行时系统才会调用一次静态构造函数。

类的第一个寻址可以是以下之一:

  • 类的首次实例化

  • 静态方法的首次调用

  • 首次访问公共静态属性

注意

此列表不完整。还有其他操作(与继承相关)可首次寻址类。

静态构造函数的典型用例是使用非初始值的静态属性的动态初始化。因此,运行时在创建实例、调用静态方法或寻址静态属性之前调用静态构造函数非常重要。

从语法角度来看,构造方法具有以下属性:

  • 具有保留名称 class_construction 的公共静态方法

  • 无参数或异常

注意

静态构造函数不得具有签名,因为无法准确判断首次寻址类的时间。

提示

在 ADT 中,您可以使用快速修复生成类构造器方法。要使用此快速修复,请执行以下操作:

  1. 在定义或实施部分中,将光标置于类名称中,然后按 Ctrl + 1。2. 从可用快速修复列表中,选择生成类构造函数

如何 缓解腹胀和排气

演示观看演示(SAP 官方交互式练习)

使用私有属性和构造函数

在本练习中,将类的静态属性设置为只读,并将实例属性设置为私有。使用构造函数设置它们的值。

模板:

  • /LRN/CL_S4D400_CLS_METHODS(全局类)

解决方案:

  • /LRN/CL_S4D400_CLS_CONSTRUCTOR(全局类)

任务 1: 复制模板(可选)

复制模板类。如果已完成上一练习,则可以跳过此任务并继续编辑类(ZCL_##_METHODSZCL_##_LOCAL_CLASSZCL_##_INSTANCES)。

步骤

  1. 将类 /LRN/CL_S4D400_CLS_METHODS 复制到您自己的包中的类(建议名称:ZCL_##_CONSTRUCTOR,其中 ## 代表组编号)。

  2. 打开全局类 /LRN/CL_S4D400_CLS_METHODS 的源代码。

  3. 将项目浏览器视图与编辑器链接。

  4. 在项目浏览器视图中,右键单击类 /LRN/CL_S4D400_CLS_METHODS 以打开上下文菜单。

  5. 从上下文菜单中,选择复制 ....

  6. 在包字段中输入的名称。在名称字段中,输入名称 ZCL_##_CONSTRUCTOR,其中 ## 代表组编号。

  7. 调整描述,然后选择下一步

  8. 确认传输请求并选择完成

任务 2: 将属性设为私有

更改属性 carrier_idconnection_id 的可见性以强制使用方法 set_attributes()get_output()

步骤

  1. 使用快速修复将属性 carrier_id 的可见性设置为私有。

  2. 切换到局部类型标签。

  3. 转到本地类 lcl_connection 中属性 carrier_id 的声明。

  4. 将光标置于 carrier_id 上并按 Ctrl + 1

  5. 双击建议将 carrier_id 设为私有

  6. 检查属性 carrier_id 的声明是否已移至专用部分。

  7. 使用快速修复将属性 connection_id 的可见性设置为专用。

  8. 转到本地类 **lcl_**connection 中属性 connection_id 的声明。

  9. 将光标置于 connection_id 上并按 Ctrl + 1

  10. 双击建议 将 connection_id 设为私有

  11. 检查属性 connection_id 的声明是否已移至专用部分。

任务 3: 创建实例构造函数

将公共方法 set_attributes 替换为实例构造函数,以确保在创建新实例期间设置属性,并且之后不会更改这些属性。

步骤

  1. 注释方法 set_attributes 的定义和实施。

  2. 选择属于 METHODS set_attributes 语句的所有行,包括带有句点符号 (.) 的行。

  3. Ctrl + < 在每个选定行前添加星号 (*)。

  4. 选择属于方法 set_attributes 实施的所有行,包括相应的 ENDMETHOD. 语句,然后再次按 Ctrl + <

  5. 使用快速修复将实例构造函数添加到本地类 lcl_connection。确保构造函数具有与特性 carrier_idconnection_id 对应的导入参数。

  6. 将光标置于类名称上,然后按 Ctrl + 1

  7. 双击生成构造函数

  8. 在对话框中,确保已选择 carrier_idconnection_id,然后选择完成

  9. 扩展生成的构造函数定义。将异常 CX_ABAP_INVALID_VALUE 添加到方法**构造函数的**定义。

  10. 导航到生成的方法**构造函数**定义。

  11. 如下调整代码:

METHODS constructor
IMPORTING
i_connection_id TYPE /dmo/connection_id
i_carrier_id    TYPE /dmo/carrier_id
RAISING
cx_ABAP_INVALID_VALUE.
  1. 扩展生成的构造函数实施。如果任一导入参数为初始值,则引发异常 CX_ABAP_INVALID_VALUE

  2. 从定义导航到方法**构造函数的实施,例如,通过将光标放在构造函数**上并按 F3

  3. 如下调整代码:

METHOD constructor.
IF i_carrier_id IS INITIAL OR i_connection_id IS INITIAL.
RAISE EXCEPTION TYPE cx_abap_invalid_value.
ENDIF.
me->connection_id = i_connection_id.
me->carrier_id    = i_carrier_id.
ENDMETHOD.
  1. 在构造函数实现中,添加 语句以将静态属性 conn_counter 的值增加 1。确保仅在未引发异常时才执行该语句。

  2. 如下调整代码:

me->connection_id = i_connection_id.
me->carrier_id    = i_carrier_id.
conn_counter = conn_counter + 1.
ENDMETHOD.
  1. 确保无法从类外部更改 conn_counter 的值。

  2. READ-ONLY 添加到静态属性 conn_counter 的声明中。

  3. 如下调整代码:

CLASS-DATA conn_counter TYPE i READ-ONLY.

任务 4: 使用构造函数

调整类 lcl_connection 的实例化以提供实例构造函数的参数并处理异常。

步骤

  1. 在方法 if_oo_adt_classrun~main 中,转到 lcl_connection 第一个实例的 NEW #() 表达式。

  2. 切换到全局类标签。

  3. 使用 connection = NEW #( ). 查找第一行。

  4. 在 NEW 表达式中,提供**构造**函数的导入参数。

提示

可以复制从此实例的方法 set_attributes 调用传递的参数。

  1. 按如下所示调整 NEW 表达式:

connection = NEW #(
i_carrier_id    = 'LH'
i_connection_id = '0400'
).
  1. 移除或注释掉方法 set_attributes 的所有调用。

  2. 选择属于 connection->set_attributes( ... ). 语句的所有行,包括带有右括号和句点 (.) 的行。

  3. Ctrl + < 在每个选定行前添加星号 (*)。

  4. 将实例创建移动到异常处理的 TRY 块中。

  5. TRY. 语句上移到实例创建之前。

  6. 您的第一个实例创建现在应如下所示:

TRY.
connection = NEW #(
i_carrier_id    = 'LH'
i_connection_id = '0400'
).
*        connection->set_attributes(
*          EXPORTING
*            i_carrier_id    = 'LH'
*            i_connection_id = '0400'
*        ).
APPEND connection TO connections.
CATCH cx_abap_invalid_value.
out->write( `Method call failed` ).
ENDTRY.
  1. 对局部类的其他实例重复上述步骤。

  2. 完成此步骤后,方法 if_oo_adt_classrun~main 的实施应如下所示:

METHOD if_oo_adt_classrun~main.
DATA connection TYPE REF TO lcl_connection.
DATA connections TYPE TABLE OF REF TO lcl_connection.
* First Instance
**********************************************************************
TRY.
connection = NEW #(
i_carrier_id    = 'LH'
i_connection_id = '0400'
).
*        connection->set_attributes(
*          EXPORTING
*            i_carrier_id    = 'LH'
*            i_connection_id = '0400'
*        ).
APPEND connection TO connections.
CATCH cx_abap_invalid_value.
out->write( `Method call failed` ).
ENDTRY.
* Second instance
**********************************************************************
TRY.
connection = NEW #(
i_carrier_id    = 'AA'
i_connection_id = '0017'
).
APPEND connection TO connections.
CATCH cx_abap_invalid_value.
out->write( `Method call failed` ).
ENDTRY.
* Third instance
**********************************************************************
TRY.
connection = NEW #(
i_carrier_id    = 'SQ'
i_connection_id = '0001'
).
APPEND connection TO connections.
CATCH cx_abap_invalid_value.
out->write( `Method call failed` ).
ENDTRY.
* Output
**********************************************************************
LOOP AT connections INTO connection.
out->write( connection->get_output( ) ).
ENDLOOP.
ENDMETHOD.
  1. 激活此类。执行并调试实例化。

  2. Ctrl + F3 激活类。

  3. F9 键运行类。