如何解决解析 ABAP 动态调用的方法:考虑的类型顺序
我尝试激发 ABAP Keyword Documentation 7.50 中描述的行为,但失败了。它与 CALL METHOD - dynamic_meth
的替代方案 2 一起给出:
... oref 可以是任何类引用变量...指向一个对象,该对象包含在 meth_name 中指定的方法...。该方法先在静态类型中搜索,然后在oref的动态类型中搜索
我使用下面给出的测试代码。 oref
的静态类型为 CL1
,动态类型为 CL2
。那么动态 CALL METHOD
语句不应该调用 M
中的方法 CL1
吗?
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'?
更新:
我想回答我最初问题的(前四)条评论。由于冗长的代码片段,我通过增加我的帖子而不是评论来回答。
确实,原始问题的代码片段的行为是标准的 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
解决方法
这与其说是类继承,不如说是接口实现。 ABAP语言帮助的意思是这样的:
假设你有一个声明方法的接口
INTERFACE some_interface PUBLIC.
METHODS some_method RETURNING VALUE(result) TYPE string.
ENDINTERFACE.
和一个实现它的类,但同时也声明了一个自己的同名方法
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.
然后对带有接口类型的引用变量的动态调用将选择接口方法而不是类自己的方法
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。
你实际上是在回答你自己的问题,不是吗?
在您的第一个示例中,您对类型为 call method
的变量对方法 m
执行 cl1
。运行时查找类 cl1
,并在那里找到请求的方法 m
。然后调用该方法。但是,您的变量实际上具有类型 cl2
,它是 cl1
的子类,它覆盖了该方法 m
。所以调用有效地达到了方法的重新定义,而不是超类的原始实现。正如您和评论者总结的那样:这是标准的面向对象行为。
请注意,这与您从文档中引用的 static-vs-dynamic 语句在本质上完全无关。方法 m
静态存在于 cl1
中,因此不涉及任何动态查找。我假设您正在寻找一种方法来探究此语句的含义,但此示例并未解决此问题。
然而,你的第二个例子正好击中了头上的钉子。让我用不同的名字重新写一遍来讨论它。给定一个空的超类 super_class
:
CLASS super_class DEFINITION.
ENDCLASS.
以及继承它的子类 sub_class
:
CLASS sub_class DEFINITION
INHERITING FROM super_class.
PUBLIC SECTION.
METHODS own_method.
ENDCLASS.
现在,由于 super_class
为空,sub_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
there。确实如此。该语句被接受并且调用被定向到own_method
。此处发生的这种额外工作正是文档中描述的“首先在静态类型中搜索此方法,然后在 oref 的动态类型中搜索此方法”。
我们在这里看到的与硬编码的方法调用不同,但它也不“非法”。本质上,这里的运行时首先将变量 cast
cut
转换为它的动态类型 sub_class
,然后再次查找可用方法。好像我们在写 DATA(casted) = CAST super_class( cut ). casted->own_method( )
。我不能说为什么运行时会这样。当语句在其整个生命周期中不断演变并且需要保持向后兼容时,这感觉就像我们通常在 ABAP 中发现的那种轻松行为。
有一个细节需要额外处理:文档中的小词“then”。为什么说它首先查看静态类型,然后查看动态类型很重要?在上面的例子中,它可以简单地说“和/或”。
为什么这个细节可能很重要,我在几天前发布的对您的问题的第二个回答中进行了描述。让我在这里再次总结一下,所以这里的答案是完整的。给定一个带有方法 some_method
的接口:
INTERFACE some_interface PUBLIC.
METHODS some_method RETURNING VALUE(result) TYPE string.
ENDINTERFACE.
和一个实现它的类,但也添加了另一个自己的方法,名称完全相同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( )
一致。
如果运行时采取相反的方式,首先检查动态类型,然后检查静态类型,它将采取不同的行动,而是调用类自己的方法 some_method
。
顺便说一下,没有办法调用类自己的 some_method
。尽管文档建议运行时会在第二步中考虑 cut
的动态类型 some_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` ).
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。