Разрешение метода динамического вызова ABAP: порядок рассмотренных типов

#abap

#abap

Вопрос:

Я пытаюсь вызвать поведение, описанное в документации по ключевым словам ABAP 7.50, но терпит неудачу. Это задается альтернативой 2 из CALL METHOD - dynamic_meth :

ВЫЗОВИТЕ МЕТОД oref->(meth_name) …

Эффект

… 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. Также верно, что для вызовов со статическим именем метода и классом типы разрешаются так, как указано в ссылке. Но тогда:

  1. Почему документация по ключевым словам ABAP содержит утверждение, на которое я ссылался?
  2. Вызовы с динамическими именами методов выполняют поиск имени метода в динамическом типе, как показано в следующем фрагменте кода. Это, конечно, не стандартное поведение 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 amp;amp; ' 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
 

Комментарии:

1. Нет, так не должно быть. Полиморфизм по-прежнему применяется здесь независимо от того, используете ли вы статический или динамический вызов метода.

2. Статические и динамические применяются здесь скорее к статическим (классовым) методам и динамическим (ссылочным) методам.

3. Это стандартное поведение OO. По крайней мере, было бы очень странно, если бы в случае динамического вызова methid вызов происходил на более высоком уровне в иерархии классов.

4. Он соответствует другим языкам и описан в справке : каждая ссылка, указывающая на объект подкласса, использует переопределенный метод, даже если он был введен со ссылкой на суперкласс. В частности, это также относится к собственной ссылке me. … В переопределенном методе super->meth может использоваться для доступа к скрытому методу, например, для применения и дополнения его функций.

5. Понятия не имею, что означает этот текст в документации ABAP. Может быть, внутренние технические вещи, касающиеся производительности поиска? (т. Е., Если у вас есть дополнительный метод N CL2 , и вы делаете CALL METHOD oref->('N') это, сначала выполните поиск N CL1 , а поскольку он не существует, он будет затем искать N CL2 )

Ответ №1:

Вы на самом деле отвечаете на свой собственный вопрос, не так ли?

В вашем первом примере вы выполняете a 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 as being sub_class . Затем он смотрит, находит ли он own_method там. И действительно, это так. Оператор принят, и вызов направлен на own_method . Это дополнительное усилие, которое здесь происходит, — это именно то, что описано в документации как «Этот метод ищется сначала в статическом типе, затем в динамическом типе oref«.

То, что мы видим здесь, отличается от жестко запрограммированных вызовов методов, но это также не является «незаконным». По сути, среда выполнения здесь сначала cast приводит переменную cut к ее динамическому типу sub_class , а затем снова ищет доступные методы. Как если бы мы писали DATA(casted) = CAST super_class( cut ). casted->own_method( ) . Я не могу сказать, почему среда выполнения действует таким образом. Это похоже на расслабленное поведение, которое мы обычно находим в ABAP, когда операторы развиваются на протяжении всего срока их службы и должны оставаться обратно совместимыми.

Есть одна деталь, которая требует дополнительного рассмотрения: крошечное слово «тогда» в документации. Почему важно сказать, что сначала он выглядит в статическом типе, а затем в динамическом типе? В приведенном выше примере вместо этого может быть просто указано «и / или».

Почему эта деталь может быть важна, описано в моем втором ответе на ваш вопрос, который я опубликовал несколько дней назад. Позвольте мне еще раз кратко изложить это здесь, чтобы этот ответ был полным. Задан интерфейс с методом 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` ).
 

Комментарии:

1. Спасибо за ваш очень подробный ответ. Просто для справки: я в основном повторно внедряю механизм разрешения ABAP в Java. Он применяется во время компиляции и для вызова должен выдавать все целевые объекты вызова, которые могут быть выбраны во время выполнения — и только те, конечно. Ваш ответ очень помогает.

2. Ах, это объясняет тот высокий уровень детализации, который вам здесь требуется. 👍

Ответ №2:

Это скорее касается реализаций интерфейса, чем наследования классов. Что означает справка по языку 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~ и требует, чтобы мы добавили an alias , чтобы это работало.

Кстати, как некоторые люди упоминали в комментариях, «статический» здесь не относится CLASS-METHODS , в отличие от методов «экземпляра». «Статический тип» и «динамический тип» относятся к разным вещам, см. Раздел «Статический тип» и «Динамический тип» в справочной статье «Правила назначения ссылочных переменных«.

Комментарии:

1. Спасибо, что указали на связь с интерфейсами. Два комментария: 1) Вы говорите Only if the runtime cannot find a method with the given name in the static type will it start looking for a method with that name in the dynamic type. This is different from regular method calls ... , что я пытался спровоцировать такое поведение, закомментировав объявление метода в some_interface и его реализацию в some_class , но оба вызова завершаются неудачей, т. Е. Ведут себя одинаково. Не могли бы вы привести пример рабочего кода? 2) Как вы объясните поведение, продемонстрированное вторым фрагментом кода в моем исходном сообщении?

2. Извините, я больше не могу редактировать свой комментарий. provide a working code example Я имел в виду пример кода, который демонстрирует вашу точку зрения, то есть разницу в поведении.