接口 练习

接口 练习

#practice #interface #oo-design

📌核心模式
关键词 答案
接口定义 INTERFACES ~ 操作符访问,或使用 ALIASES 创建别名
多接口 一个类可以实现多个接口,弥补单继承的限制
接口多态 通过接口引用调用方法,实际执行类实现版本

题目1 - [记忆] 接口的定义和特点

请说明ABAP中接口(Interface)的定义方式和主要特点。

📌查看答案
接口定义语法:
1
2
3
4
INTERFACE lif Printable.
METHODS: print.
DATA: max_lines TYPE i.
ENDINTERFACE.

主要特点:

  1. 纯抽象:接口只包含声明(方法、属性、事件),不包含任何实现代码
  2. 无实例化:不能直接创建接口的实例(没有 CREATE OBJECT),只能创建实现该接口的类的对象
  3. 公有可见:接口中的所有成员默认是公有的,没有可见性分区(无PUBLIC/PROTECTED/PRIVATE)
  4. 命名约定:局部接口以 LIF_ 开头,全局接口以 IF_ 开头
  5. 实现语法:类使用 INTERFACES lif_name 来实现接口
  6. 设计作用:定义行为契约,实现类必须提供所有接口方法的实现

题目2 - [记忆] 接口与类的区别

请列举接口(Interface)和类(Class)之间的五个主要区别。

📌查看答案
特性 接口(Interface) 类(Class)
实现代码 仅包含声明,无实现代码 包含完整的实现
实例化 不能创建实例 可以创建实例(除抽象类外)
可见性 所有成员自动为公有 可定义PUBLIC/PROTECTED/PRIVATE
多重性 一个类可以实现多个接口 一个类只能继承一个父类
成员类型 可包含方法、属性、事件、常量 可包含所有类型的成员,包括构造函数
继承 接口可以继承其他接口(复合接口) 类单继承一个父类
设计用途 定义行为契约(”能做什么”) 定义对象的结构和行为(”是什么”)

题目3 - [记忆] 波浪号(~)操作符

请说明ABAP中波浪号(~)操作符在接口上下文中的用途和使用方式。

📌查看答案
波浪号()操作符用于限定接口方法的归属,格式为 `interface_namemethod_name`。

用途场景:

  1. 类实现中:在 IMPLEMENTATION 部分实现接口方法时

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    CLASS lcl_report DEFINITION.
    PUBLIC SECTION.
    INTERFACES lif_printable.
    ENDCLASS.

    CLASS lcl_report IMPLEMENTATION.
    METHOD lif_printable~print.
    " 实现接口的print方法
    WRITE: / 'Printing report'.
    ENDMETHOD.
    ENDCLASS.
  2. 外部调用时:通过对象引用调用接口方法时(如果不使用别名)

    1
    2
    DATA(lo_report) = NEW lcl_report( ).
    lo_report->lif_printable~print( ).

注意:通过接口引用调用时不需要波浪号:

1
2
3
DATA: lo_printable TYPE REF TO lif_printable.
lo_printable = NEW lcl_report( ).
lo_printable->print( ). " 直接调用,无需 ~

题目4 - [记忆] 别名(ALIASES)

请说明ABAP中别名(ALIASES)的作用和语法。

📌查看答案
别名的作用:为接口中的方法创建一个更简短、更直观的替代名称,避免使用冗长的 interface~method 语法。

语法:

1
ALIASES alias_name FOR interface_name~method_name.

示例:

1
2
3
4
5
6
CLASS lcl_report DEFINITION.
PUBLIC SECTION.
INTERFACES lif_printable.
ALIASES print FOR lif_printable~print.
ALIASES get_page_count FOR lif_printable~get_page_count.
ENDCLASS.

使用别名后:

1
2
3
DATA(lo_report) = NEW lcl_report( ).
lo_report->print( ). " 使用别名,而非 lo_report->lif_printable~print( )
lo_report->get_page_count( ). " 使用别名

优点:

  1. 简化代码,提高可读性
  2. 隐藏接口名称的实现细节
  3. 当类实现多个接口时,别名可以消除同名方法的歧义

题目5 - [记忆] 接口引用与多态

请说明如何通过接口引用实现多态。

📌查看答案
接口引用实现多态的方式:
  1. 声明接口引用变量

    1
    DATA: lo_serializable TYPE REF TO lif_serializable.
  2. 将实现类的对象赋给接口引用(类似向上转换):

    1
    2
    lo_serializable = NEW lcl_order( ).   " lcl_order 实现了 lif_serializable
    lo_serializable = NEW lcl_product( ). " lcl_product 也实现了 lif_serializable
  3. 通过接口引用调用方法

    1
    lo_serializable->to_json( ).

    实际执行的是具体类(lcl_order 或 lcl_product)的实现版本

优势

  • 调用者只需要知道接口定义,不需要知道具体的实现类
  • 可以在运行时替换不同的实现,符合”依赖倒置原则”
  • 可以创建接口引用的内表,统一管理不同类型的对象
    1
    2
    3
    4
    5
    DATA: lt_objects TYPE TABLE OF REF TO lif_serializable.
    " 可以包含 lcl_order、lcl_product 等各种实现类的对象
    LOOP AT lt_objects INTO DATA(lo_obj).
    lo_obj->to_json( ). " 多态调用
    ENDLOOP.

题目6 - [应用] 实现多个接口

请编写一个类 lcl_document,同时实现两个接口 lif_printable(含方法 print)和 lif_saveable(含方法 save)。要求使用别名简化方法调用。

📌查看答案
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
" 接口定义
INTERFACE lif_printable.
METHODS: print.
ENDINTERFACE.

INTERFACE lif_saveable.
METHODS: save
IMPORTING iv_path TYPE string.
ENDINTERFACE.

" 类定义
CLASS lcl_document DEFINITION.
PUBLIC SECTION.
INTERFACES: lif_printable, lif_saveable.
ALIASES: print FOR lif_printable~print,
save FOR lif_saveable~save.
METHODS: constructor IMPORTING iv_title TYPE string.
PRIVATE SECTION.
DATA: mv_title TYPE string.
ENDCLASS.

" 类实现
CLASS lcl_document IMPLEMENTATION.
METHOD constructor.
mv_title = iv_title.
ENDMETHOD.

METHOD lif_printable~print.
WRITE: / |打印文档: { mv_title }|.
ENDMETHOD.

METHOD lif_saveable~save.
WRITE: / |保存文档 '{ mv_title }' 到路径: { iv_path }|.
ENDMETHOD.
ENDCLASS.

使用方式:

1
2
3
4
5
6
7
8
9
" 通过别名调用
DATA(lo_doc) = NEW lcl_document( iv_title = '技术规格书' ).
lo_doc->print( ).
lo_doc->save( iv_path = '/tmp/spec.pdf' ).

" 通过接口引用调用(多态)
DATA: lo_printable TYPE REF TO lif_printable.
lo_printable = lo_doc.
lo_printable->print( ).

题目7 - [应用] 接口多态应用场景

系统中有三种日志记录器:文件日志(lcl_file_logger)、数据库日志(lcl_db_logger)和邮件日志(lcl_email_logger)。请设计一个接口使它们可以互换使用,并编写调用代码在运行时选择不同的日志记录器。

📌查看答案

接口设计

1
2
3
4
5
INTERFACE lif_logger.
METHODS:
log_info IMPORTING iv_message TYPE string,
log_error IMPORTING iv_message TYPE string.
ENDINTERFACE.

实现类(示例一个,其余类似)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
CLASS lcl_file_logger DEFINITION.
PUBLIC SECTION.
INTERFACES lif_logger.
ALIASES: log_info FOR lif_logger~log_info,
log_error FOR lif_logger~log_error.
ENDCLASS.

CLASS lcl_file_logger IMPLEMENTATION.
METHOD lif_logger~log_info.
WRITE: / |[FILE-INFO] { iv_message }|.
ENDMETHOD.
METHOD lif_logger~log_error.
WRITE: / |[FILE-ERROR] { iv_message }|.
ENDMETHOD.
ENDCLASS.

运行时选择(多态调用)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
DATA: lo_logger TYPE REF TO lif_logger.

" 根据配置选择不同的日志记录器
CASE lv_log_type.
WHEN 'FILE'.
lo_logger = NEW lcl_file_logger( ).
WHEN 'DB'.
lo_logger = NEW lcl_db_logger( ).
WHEN 'EMAIL'.
lo_logger = NEW lcl_email_logger( ).
ENDCASE.

" 统一调用,不需要知道具体实现
lo_logger->log_info( '系统启动' ).
lo_logger->log_error( '连接超时' ).

设计优势

  1. 新增日志记录器时只需实现接口,无需修改调用代码
  2. 运行时可灵活切换实现
  3. 符合开闭原则和依赖倒置原则

题目8 - [分析] 接口vs继承的设计选择

项目中需要设计一个支付系统。需求如下:

  • 有多种支付方式:信用卡、支付宝、微信支付、银行转账
  • 某些支付方式支持退款(如信用卡、支付宝),某些不支持(如某些银行转账)
  • 某些支付方式支持分期付款(如信用卡),某些不支持

请分析:应该使用继承还是接口来设计?请给出你的设计方案和理由。

📌查看答案

分析:接口优于继承

理由:

  1. 支付方式之间没有”is-a”层次关系:信用卡、支付宝等都是独立的支付手段,不存在谁是谁的子类的层次关系
  2. 行为组合而非层次继承:退款和分期是独立的能力(行为),不是类层次结构
  3. 灵活性:同一个支付方式可能有多个独立的能力组合

推荐设计方案(基于接口)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
" 基础支付接口(所有支付方式必须实现)
INTERFACE lif_payment.
METHODS: pay IMPORTING iv_amount TYPE p.
ENDINTERFACE.

" 退款能力接口(可选实现)
INTERFACE lif_refundable.
METHODS: refund IMPORTING iv_amount TYPE p.
ENDINTERFACE.

" 分期能力接口(可选实现)
INTERFACE lif_installment.
METHODS: pay_in_installments
IMPORTING iv_amount TYPE p
iv_months TYPE i.
ENDINTERFACE.

实现示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
" 信用卡:支付 + 退款 + 分期
CLASS lcl_credit_card DEFINITION.
PUBLIC SECTION.
INTERFACES: lif_payment, lif_refundable, lif_installment.
ENDCLASS.

" 支付宝:支付 + 退款
CLASS lcl_alipay DEFINITION.
PUBLIC SECTION.
INTERFACES: lif_payment, lif_refundable.
ENDCLASS.

" 银行转账:仅支付
CLASS lcl_bank_transfer DEFINITION.
PUBLIC SECTION.
INTERFACES: lif_payment.
ENDCLASS.

调用方式

1
2
3
4
5
6
7
8
" 所有支付方式统一处理
DATA: lt_payments TYPE TABLE OF REF TO lif_payment.

" 检查是否支持退款
IF lo_payment IS INSTANCE OF lif_refundable.
DATA(lo_refundable) = CAST lif_refundable( lo_payment ).
lo_refundable->refund( iv_amount = '100.00' ).
ENDIF.

设计原则总结

  • “是什么”(身份)用继承
  • “能做什么”(能力/行为)用接口
  • 支付方式的核心是”行为能力”的组合,因此接口是最合适的设计选择

📌模式总结
关键词 答案
接口定义 INTERFACE … ENDINTERFACE,纯声明无实现,成员默认公有
接口 vs 类 接口无实现、无实例、可多实现;类有实现、可实例化、单继承
~操作符 interface~method 格式,在实现和通过对象引用调用时使用
ALIASES 为 interface~method 创建简短别名,提高可读性
接口多态 接口引用指向不同实现类对象,调用同一方法执行不同行为
多接口实现 INTERFACES if1, if2, …,一个类可实现多个接口
设计选择 “是什么”用继承,”能做什么”用接口