Функциональный консультант иногда изучает код программы, чтобы понять в деталях как работает функционал. Курсы, help.sap.com, F1 не всегда помогают и приходится читать листинг программы или смотреть, что происходит в отладчике. Если с конструкциями SELECT, LOOP AT, READ TABLE и т.д. еще более или менее понятно, помогает изучение программирования в школе или институте, элементарная логика или примерное знание функционала и структуры данных, то с конструкциями типа ->* или ?= могут возникнуть сложности (как минимум у меня). в продолжении статьи Полиморфизм в ABAP решил разобраться что такое разименование ->* и приведение типов ?= в ABAP.
Ссылочная переменная
Прежде всего нужно понять что такое ссылочная переменная. Обычная переменная – это именованный объект данных, используемый для хранения значения в выделенной области памяти программы. Ссылочная переменная не хранит значение, как обычная переменная, или указывает, как field-symbol, на конкретную переменную. Она указывает на область памяти и содержат внутри себя адрес в памяти. При этом объект в памяти может быть, как явно связан с какой-либо переменной, так и создан анонимно и привязан только к ссылочной переменной.
Сразу нужно разобраться с field-symbols. Я считал это ссылкой и называл ссылкой, но это не так, это псевдоним (alias) для переменной, который можно определить родовыми типами (any, data, csequence, simple, any table и т.д.) и потом присвоить все что угодно, даже если типы данных не совпадают, но при этом они совместимы (ASSIGN COMPONENT ‘…’ OF STRUCTURE … TO <…>, ASSIGN … TO <…> или ASSIGN … TO <…> CASTING TYPE …). Псевдоним не хранит в себе ссылку (указатель) на область памяти, а лишь является указателем на переменную или объект данных, которые уже есть в текущем коде. Изменение одного – изменяет другое.
Настоящая ссылочная переменная хранит ссылку (указатель) на блок памяти, где хранится значение. Изменение значения не изменяет адрес и соответственно ссылочную переменную. Поэтому field-symbols постараюсь не называть по привычке ссылкой, это будет псевдоним.
Объявление ссылочных переменных: DATA … TYPE REF TO … Чтобы получить адрес относительно переменной, необходимо воспользоваться оператором GET REFERENCE OF … INTO … (или по-новому … = REF #( … )).
Разименование ->*
Значение ссылочной переменной является адресом в памяти, где хранятся данные, соответственно, для получения самого значения из памяти или его изменения необходимо выполнить специальную процедуру разыменования ->*
|
DATA ... TYPE REF TO ... "Объявляем ссылочную переменную GET REFERENCE OF ... INTO ... "Получаем ссылку на переменную (адрес блока памяти, где хранится значение переменной) ...→* = ... "Блоку памяти по адресу, указанному в ссылочной переменной, присваиваем новое значение ...→* = ...→*→... "Присваиваем значение переменной в классе другой переменной через ссылочные переменные ASSIGN ...→* TO <...> CASTING TYPE ... "Присваиваем переменную ее псевдониму |
|
CLASS CL DEFINITION. PUBLIC SECTION. DATA Bl TYPE I VALUE 1. ENDCLASS. START-OF-SELECTION. DATA: Oref TYPE REF TO CL, Dref1 LIKE REF TO Oref, Dref2 TYPE REF TO I . CREATE OBJECT Oref. GET REFERENCE OF Oref INTO Dref1. "Dref1 = REF #( Oref ) CREATE DATA Dref2. Dref2→* = Dref1→*→Bl. |
Если ссылочная переменная ссылается на структуру, доступ к её компонентам осуществляется через -><имя компонента>.
С помощью оператора CREATE DATA … можно динамически создать анонимный объект в памяти, привязав его к ссылочной переменной, время жизни такого объекта равносильно времени жизни ссылочной переменной. У оператора CREATE DATA есть возможность указать тип динамически CREATE DATA … TYPE (…). Для динамически создаваемых объектов данных разыменование является единственным способом получения их содержимого.
Возвращаясь к примеру с котиками и собачками получаем следующий код:
Объявляем класс и метод, который возвращает ссылку
|
CLASS class_animal_speech DEFINITION ABSTRACT. PUBLIC SECTION. DATA: v_anim TYPE string, v_spee TYPE string. METHODS: animal ABSTRACT RETURNING value(r_anim) TYPE REF TO string, "Разный синтаксис для возврата параметра speech ABSTRACT EXPORTING r_spee TYPE REF TO string. "Разный синтаксис для возврата параметра ENDCLASS. |
определяем метод:
|
CLASS class_dog IMPLEMENTATION. METHOD animal. v_anim = 'Собака'. GET REFERENCE OF v_anim INTO r_anim. "r_anim = REF #( v_anim ) ENDMETHOD. METHOD speech. v_spee = 'Гав'. GET REFERENCE OF v_spee INTO r_spee. "r_spee = REF #( v_spee ) ENDMETHOD. ENDCLASS. |
и определяем как это будет использоваться:
|
CLASS class_print IMPLEMENTATION. METHOD print. DATA: ref TYPE REF TO string. WRITE: 'Животное'. ref = animal_speech→animal( ). "Разный синтаксис для получения параметра WRITE: ref→*. WRITE: 'говорит'. CALL METHOD animal_speech→speech IMPORTING r_spee = ref. "Разный синтаксис для получения параметра WRITE: ref→*. ENDMETHOD. ENDCLASS. |
Листинг программы
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 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
|
*---------------------------------------------------------------------* * Report zmiastest_de_referencing *---------------------------------------------------------------------* * Пример с разименованием *---------------------------------------------------------------------* REPORT zmiastest_de_referencing. CLASS class_animal_speech DEFINITION ABSTRACT. PUBLIC SECTION. DATA: v_anim TYPE string, v_spee TYPE string. METHODS: animal ABSTRACT RETURNING value(r_anim) TYPE REF TO string, "Разный синтаксис для возврата параметра speech ABSTRACT EXPORTING r_spee TYPE REF TO string. "Разный синтаксис для возврата параметра ENDCLASS. CLASS class_dog DEFINITION INHERITING FROM class_animal_speech. PUBLIC SECTION. METHODS: animal REDEFINITION, speech REDEFINITION. ENDCLASS. CLASS class_cat DEFINITION INHERITING FROM class_animal_speech. PUBLIC SECTION. METHODS: animal REDEFINITION, speech REDEFINITION. ENDCLASS. CLASS class_print DEFINITION. PUBLIC SECTION. CLASS-METHODS: print IMPORTING animal_speech TYPE REF TO class_animal_speech. ENDCLASS. CLASS class_dog IMPLEMENTATION. METHOD animal. v_anim = 'Собака'. GET REFERENCE OF v_anim INTO r_anim. "r_anim = REF #( v_anim ) ENDMETHOD. METHOD speech. v_spee = 'Гав'. GET REFERENCE OF v_spee INTO r_spee. "r_spee = REF #( v_spee ) ENDMETHOD. ENDCLASS. CLASS class_cat IMPLEMENTATION. METHOD animal. v_anim = 'Кошка'. GET REFERENCE OF v_anim INTO r_anim. "r_anim = REF #( v_anim ) ENDMETHOD. METHOD speech. v_spee = 'Мяу'. GET REFERENCE OF v_spee INTO r_spee. "r_spee = REF #( v_spee ) ENDMETHOD. ENDCLASS. CLASS class_print IMPLEMENTATION. METHOD print. DATA: ref TYPE REF TO string. WRITE: 'Животное'. ref = animal_speech→animal( ). "Разный синтаксис для получения параметра WRITE: ref→*. WRITE: 'говорит'. CALL METHOD animal_speech→speech IMPORTING r_spee = ref. "Разный синтаксис для получения параметра WRITE: ref→*. ENDMETHOD. ENDCLASS. START-OF-SELECTION. DATA: dog TYPE REF TO class_dog, cat TYPE REF TO class_cat. CREATE OBJECT dog. CREATE OBJECT cat. CALL METHOD class_print⇒print( dog ). "Разный синтаксис для передачи параметра NEW-LINE. CALL METHOD class_print⇒print EXPORTING animal_speech = cat. "Разный синтаксис для передачи параметра |
Приведение типов
При присваивании между ссылочными переменными общий оператор присваивания = может использоваться только для приведений вверх, в которых статический тип source_ref более специфичен или совпадает со статическим типом destination_ref. Иными словами, если есть класс-родитель и класс-наследник, то ссылочную переменную на класс-наследник можно присвоить ссылочной переменной класса-родителя.
Специальный оператор приведения ?= (или по-новому … = CAST #( … )) может использоваться только для присваивания между ссылочными переменными. Если статический тип source_ref является более общим, чем статический тип destination_ref, ?= должен использоваться для создания приведения вниз. Если это известно статически, оно проверяется с помощью проверки синтаксиса, в противном случае оно проверяется во время выполнения. Фактическое приведение вниз, то есть проверка того, возможны ли назначения в соответствии с правилами назначения для ссылочных переменных, выполняется только во время выполнения.
На примере собак. Есть абстрактный класс собак c методами которые умет что-то делать, например, определяют как собака говорит:
|
CLASS class_dog_general IMPLEMENTATION. METHOD animal. v_anim = 'Собака'. GET REFERENCE OF v_anim INTO r_anim. "r_anim = REF #( v_anim ) ENDMETHOD. METHOD speech. v_spee = 'Гав'. GET REFERENCE OF v_spee INTO r_spee. "r_spee = REF #( v_spee ) ENDMETHOD. ENDCLASS. |
Есть более специфичные классы наследники, которые определяют подкласс служебных и домашних собак через специализированные методы:
|
CLASS class_dog_home IMPLEMENTATION. METHOD service. v_serv = 'Домашняя'. GET REFERENCE OF v_serv INTO r_serv. "r_serv = REF #( v_serv ) ENDMETHOD. ENDCLASS. CLASS class_dog_service IMPLEMENTATION. METHOD service. v_serv = 'Служебная'. GET REFERENCE OF v_serv INTO r_serv. "r_serv = REF #( v_serv ) ENDMETHOD. ENDCLASS. |
И есть метод, который все это выводит:
|
CLASS class_print IMPLEMENTATION. METHOD print_dog. DATA: ref TYPE REF TO string. WRITE: 'животное'. ref = dog_general→animal( ). WRITE: ref→*. WRITE: 'говорит'. ref = dog_general→speech( ). WRITE: ref→*. WRITE: 'является:'. CALL METHOD dog_general→service IMPORTING r_serv = ref. WRITE: ref→*. ENDMETHOD. ENDCLASS. |
Что бы метод более общего класса class_dog_general можно было использовать для объектов, созданных для подклассов, нужно передать ссылку на общий класс через ?=
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
START-OF-SELECTION. DATA: dog TYPE REF TO class_dog_general, hom TYPE REF TO class_dog_home, ser TYPE REF TO class_dog_service. hom ?= dog. "hom = CAST #( dog ) CREATE OBJECT hom. CALL METHOD class_print⇒print_dog EXPORTING dog_general = hom. ser ?= dog. "ser = CAST #( dog ) CREATE OBJECT ser. CALL METHOD class_print⇒print_dog EXPORTING dog_general = ser. |
Выигрыш будет в том, что метод общего для всех класса определен один раз, а потом используется для объектов специализированных классов.
Если статический тип целевой переменной более специфичен, чем статический тип исходной переменной, во время выполнения, перед выполнением присваивания, необходимо проверить, является ли он менее специфичным или таким же, как динамический тип исходной переменной. Название down cast происходит от того факта, что движение в пространстве наследования происходит вниз. Поскольку целевая переменная может принимать меньшее количество динамических типов по сравнению с исходной переменной, это присвоение также называется сужающим приведением. Приведение вниз всегда должно выполняться явно.
Листинг программы
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 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
|
*---------------------------------------------------------------------* * Report zmiastest_downcast *---------------------------------------------------------------------* * Пример с переопределением типов вниз *---------------------------------------------------------------------* REPORT zmiastest_downcast. CLASS class_animal_speech DEFINITION ABSTRACT. PUBLIC SECTION. DATA: v_anim TYPE string, v_spee TYPE string. METHODS: animal ABSTRACT RETURNING VALUE(r_anim) TYPE REF TO string, speech ABSTRACT RETURNING VALUE(r_spee) TYPE REF TO string. ENDCLASS. CLASS class_dog_general DEFINITION ABSTRACT INHERITING FROM class_animal_speech. PUBLIC SECTION. DATA: v_serv TYPE string. METHODS: animal REDEFINITION, speech REDEFINITION, service ABSTRACT EXPORTING r_serv TYPE REF TO string. ENDCLASS. CLASS class_dog_home DEFINITION INHERITING FROM class_dog_general. PUBLIC SECTION. METHODS: service REDEFINITION. ENDCLASS. CLASS class_dog_service DEFINITION INHERITING FROM class_dog_general. PUBLIC SECTION. METHODS: service REDEFINITION. ENDCLASS. CLASS class_print DEFINITION. PUBLIC SECTION. CLASS-METHODS: print_dog IMPORTING dog_general TYPE REF TO class_dog_general. ENDCLASS. CLASS class_dog_general IMPLEMENTATION. METHOD animal. v_anim = 'Собака'. GET REFERENCE OF v_anim INTO r_anim. "r_anim = REF #( v_anim ) ENDMETHOD. METHOD speech. v_spee = 'Гав'. GET REFERENCE OF v_spee INTO r_spee. "r_spee = REF #( v_spee ) ENDMETHOD. ENDCLASS. CLASS class_dog_home IMPLEMENTATION. METHOD service. v_serv = 'Домашняя'. GET REFERENCE OF v_serv INTO r_serv. "r_serv = REF #( v_serv ) ENDMETHOD. ENDCLASS. CLASS class_dog_service IMPLEMENTATION. METHOD service. v_serv = 'Служебная'. GET REFERENCE OF v_serv INTO r_serv. "r_serv = REF #( v_serv ) ENDMETHOD. ENDCLASS. CLASS class_print IMPLEMENTATION. METHOD print_dog. DATA: ref TYPE REF TO string. WRITE: 'животное'. ref = dog_general→animal( ). WRITE: ref→*. WRITE: 'говорит'. ref = dog_general→speech( ). WRITE: ref→*. WRITE: 'является:'. CALL METHOD dog_general→service IMPORTING r_serv = ref. WRITE: ref→*. ENDMETHOD. ENDCLASS. START-OF-SELECTION. DATA: dog TYPE REF TO class_dog_general, hom TYPE REF TO class_dog_home, ser TYPE REF TO class_dog_service. hom ?= dog. "hom = CAST #( dog ) CREATE OBJECT hom. CALL METHOD class_print⇒print_dog EXPORTING dog_general = hom. NEW-LINE. ser ?= dog. CREATE OBJECT ser. CALL METHOD class_print⇒print_dog EXPORTING dog_general = ser. "ser = CAST #( dog ) |
Подробнее про разименование ->* и приведение типов ?= в ABAP
тут и
тут.