解析 ABAP 动态调用的方法:考虑的类型顺序

Resolving the method of a ABAP dynamic call: order of types considered

我试图挑起 ABAP Keyword Documentation 7.50 中描述的行为,但失败了。它与 CALL METHOD - dynamic_meth 的备选方案 2 一起给出:

CALL METHOD oref->(meth_name) ...

Effect

... oref can be any class reference variable ... that points to an object that contains the method ... specified in meth_name. This method is searched for first in the static type, then in the dynamic type of oref

我使用下面给出的测试代码。 oref的静态类型是CL1,动态类型是CL2。那么动态 CALL METHOD 语句不应该在 CL1 中调用方法 M 吗?

REPORT ZU_DEV_2658_DYNAMIC.

CLASS CL1 DEFINITION.
  PUBLIC SECTION.
    METHODS M.
ENDCLASS.

CLASS CL1 IMPLEMENTATION.
  METHOD M.
    write / 'original'.
  ENDMETHOD.
ENDCLASS.

CLASS CL2 DEFINITION INHERITING FROM CL1.
  PUBLIC SECTION.
    METHODS M REDEFINITION.
ENDCLASS.

CLASS CL2 IMPLEMENTATION.
  METHOD M.
    write / 'redefinition'.
  ENDMETHOD.
ENDCLASS.

START-OF-SELECTION.

DATA oref TYPE REF TO cl1.    " static type is CL1
CREATE OBJECT oref TYPE cl2.  " dynamic type is CL2

oref->m( ).                   " writes 'redefinition' - that's ok
CALL METHOD oref->('M').      " writes 'redefinition' - shouldn't that be 'original'?

更新:

我想回答对我的原始问题的(前四个)评论。由于代码片段冗长,我通过扩充 post 而不是评论来回答。

确实,原始问题的代码片段的行为是标准的 OO 行为。对于使用静态方法名称和 class 的调用,类型也是由 link 给出的,这也是事实。但是然后:

  1. 为什么 ABAP 关键字文档会做出我 link 编辑的声明?

  2. 调用动态方法名 do 在动态类型中搜索方法名,如以下代码片段所示。这当然不是标准的 OO 行为。

我的问题是:显然,搜索机制与描述的不同。描述有误还是我漏掉了什么?

REPORT ZU_DEV_2658_DYNAMIC4.

CLASS CL_A DEFINITION.
ENDCLASS.

CLASS CL_B DEFINITION INHERITING FROM CL_A.
  PUBLIC SECTION.
    METHODS M2 IMPORTING VALUE(caller) TYPE c OPTIONAL PREFERRED PARAMETER caller.
ENDCLASS.

CLASS CL_B IMPLEMENTATION.
  METHOD M2.
    write / caller && ' calls b m2'.
  ENDMETHOD.
ENDCLASS.

START-OF-SELECTION.

DATA orefaa TYPE REF TO cl_a.   
CREATE OBJECT orefaa TYPE cl_a.   " static and dynamic type is CL_A

*orefaa->m2( 'orefa->m2( )' ). syntax error: method m2 is unknown'.
*CALL METHOD orefaa->('M2') EXPORTING caller = 'CALL METHOD orefa->("M2")'. results in exception: method m2 is unknown'.

DATA orefab TYPE REF TO cl_a.     " static type is CL_A
CREATE OBJECT orefab TYPE cl_b.   " dynamic type is CL_B

*orefab->m2( 'orefab->m2( )' ). results in syntax error: method m2 is unknown'.
CALL METHOD orefab->('M2') EXPORTING caller = 'CALL METHOD orefab->("M2")'. " succeeds

这是关于接口实现而不是 class 继承。 ABAP语言帮助的意思是这样的:

假设您有一个声明方法的接口

INTERFACE some_interface PUBLIC.
  METHODS some_method RETURNING VALUE(result) TYPE string.
ENDINTERFACE.

和实现它的 class,但同时也声明了一个具有相同名称的方法,属于它自己的

CLASS some_class DEFINITION PUBLIC.
  PUBLIC SECTION.
    INTERFACES some_interface.
    METHODS some_method RETURNING VALUE(result) TYPE string.
ENDCLASS.

CLASS some_class IMPLEMENTATION.

  METHOD some_interface~some_method.
    result = `Executed the interface's method`.
  ENDMETHOD.

  METHOD some_method.
    result = `Executed the class's method`.
  ENDMETHOD.

ENDCLASS.

然后对使用接口类型化的引用变量的动态调用将选择接口方法而不是 class 自己的方法

METHOD prefers_interface_method.

  DATA cut TYPE REF TO zfh_some_interface.
  cut = NEW zfh_some_class( ).
  DATA result TYPE string.

  CALL METHOD cut->('SOME_METHOD')
    RECEIVING
      result = result.

  cl_abap_unit_assert=>assert_equals(
      act = result
      exp = `Executed the interface's method` ).

ENDMETHOD.

这实际上与我们通过常规方法调用观察到的行为完全相同,即如果我们在代码中而不是在变量中提供方法名称。

仅当运行时在静态类型中找不到具有给定名称的方法时,它才会开始在动态类型中寻找具有该名称的方法。这与常规方法调用不同,在常规方法调用中,编译器将拒绝缺少的 some_interface~ 并要求我们添加一个 alias 才能正常工作。

顺便说一句,正如某些人在评论中提到的,这里的“静态”不是指CLASS-METHODS,而是指“实例”方法。 “静态类型”和“动态类型”指的是不同的东西,见the section Static Type and Dynmic Type in the help article Assignment Rules for Reference Variables.

你实际上是在回答你自己的问题,不是吗?

在您的第一个示例中,您对类型为 cl1 的变量执行 call method 方法 m。运行时查找 class cl1,并在那里找到请求的方法 m。然后它调用该方法。但是,您的变量实际上具有 cl2 类型,cl1 的子 class,它覆盖了 m 方法。所以调用有效地到达了方法的重新定义,而不是 super-class 的原始实现。正如您和评论者总结的那样:这是标准的面向对象行为。

请注意,这在本质上与您从文档中引用的静态与动态语句完全无关。方法 m 静态 存在于 cl1 中,因此不涉及任何动态查找。我假设您正在寻找一种方法来探究此语句的含义,但此示例并未解决它。

然而,你的第二个例子恰恰一语中的。让我用不同的名字重写一遍来讨论它。给定一个空的 super class super_class:

CLASS super_class DEFINITION.
ENDCLASS.

和继承它的子class sub_class

CLASS sub_class DEFINITION
  INHERITING FROM super_class.
  PUBLIC SECTION.
    METHODS own_method.
ENDCLASS.

现在,由于 super_class 是空的,sub_class 不会接管那里的任何方法。相比之下,我们专门给这个class.

添加一个方法own_method

下面的语句序列准确地演示了动态调用的特殊之处:

DATA cut TYPE REF TO super_class.
cut = NEW sub_class( ).
CALL METHOD cut->('OWN_METHOD').
" runs sub_class->own_method

运行时遇到call method语句。它首先检查变量 cut 的静态类型,即 super_class。那里不存在请求的方法 own_method。如果这就是所发生的一切,那么调用现在将失败并出现未找到方法异常。如果我们写一个硬编码的 cut->own_method( ),我们甚至不会走到这一步——编译器已经拒绝了。

但是,call method 运行时继续。它将 cut 的动态类型确定为 sub_class。然后它看是否在own_method那里找到了。事实上,确实如此。该声明被接受,呼叫被定向到 own_method。此处发生的额外工作正是文档中描述的“首先在静态类型中搜索此方法,然后在 oref 的动态类型中搜索”。

我们在这里看到的不同于硬编码的方法调用,但它也不是“非法的”。本质上,这里的运行时首先 cast 将变量 cut 设置为它的动态类型 sub_class,然后再次查找可用的方法。就好像我们在写 DATA(casted) = CAST super_class( cut ). casted->own_method( )。我不能说为什么运行时会这样。这感觉就像我们通常在 ABAP 中发现的那种轻松的行为,当语句在其整个生命周期中不断发展并且需要保持向后兼容时。

有一个细节需要额外处理:文档中的小词“then”。为什么说它first看静态类型,then看动态类型呢?在上面的示例中,它可以简单地说“and/or”。

我几天前发布的对您问题的第二个回答中描述了为什么这个细节可能很重要。让我在这里再次总结一下,所以这里的答案是完整的。给定一个带有方法 some_method:

的接口
INTERFACE some_interface PUBLIC.
  METHODS some_method RETURNING VALUE(result) TYPE string.
ENDINTERFACE.

和实现它的 class,但也添加了自己的另一个方法,名称完全相同 some_method:

CLASS some_class DEFINITION PUBLIC.
  PUBLIC SECTION.
    INTERFACES some_interface.
    METHODS some_method RETURNING VALUE(result) TYPE string.
ENDCLASS.

CLASS some_class IMPLEMENTATION.

  METHOD some_interface~some_method.
    result = `Executed the interface's method`.
  ENDMETHOD.

  METHOD some_method.
    result = `Executed the class's method`.
  ENDMETHOD.

ENDCLASS.

CALL METHOD cut->('some_method') 现在调用了这两种方法中的哪一种?文档中的顺序描述了它:

DATA cut TYPE REF TO some_interface.
cut = NEW some_class( ).
DATA result TYPE string.

CALL METHOD cut->('SOME_METHOD')
  RECEIVING
    result = result.

cl_abap_unit_assert=>assert_equals(
    act = result
    exp = `Executed the interface's method` ).

遇到call method语句,运行时首先检查变量cut的静态类型,即some_interface。这种类型有一个方法 some_method。因此,运行时将继续调用此方法。这又是标准的面向对象。特别要注意这个例子是如何通过单独给出字符串 some_method 来调用方法 some_method 的,尽管它的完全限定名称实际上是 some_interface~some_method。这与硬编码变体 cut->some_method( ).

一致

如果运行时以相反的方式运行,首先检查动态类型,然后检查静态类型,它的行为会有所不同,而是调用 class 自己的方法 some_method

顺便说一句,没办法调用class自己的some_method。尽管文档建议运行时会在第二步考虑 cut 的动态类型 some_class,但它还补充说“在动态情况下,也只能访问接口组件,这是不可能的使用接口引用变量来访问任何类型的组件。"

调用 class 自己的方法 some_method 的唯一方法是更改​​ cut 的类型:

DATA cut TYPE REF TO some_class.
cut = NEW some_class( ).
DATA result TYPE string.

CALL METHOD cut->('SOME_METHOD')
  RECEIVING
    result = result.

cl_abap_unit_assert=>assert_equals(
    act = result
    exp = `Executed the class's method` ).