До версии языка 7.51 чтобы предоставить фиксированный набор значений, например, для передачи в методы нашего API, приходилось использовать именованные константы и вручную реализовывать контроль ввода значений этих констант. Типичный пример стандартного API по работе с SALV:
Значением по умолчанию мы могли дать представление разработчикам использующим наше API, откуда они могут взять значения констант. Однако никакого контроля со стороны языка на этапе активации система не производит, что позволяет передавать в целом любые значения из диапазона типа I. Контроль на этапе выполнения уже оставался на совести разработчика API.
Начиная с версии языка 7.51 у нас появилась возможность объявлять так называемые перечисляемые типы. Причём контроль за вводом осуществляется уже на уровне языка. Далее разберём как именно их можно использовать.
Но для начала простой вариант эмуляции перечисляемого типа до версии 7.51:
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 |
CLASS cx_wrong_size DEFINITION INHERITING FROM cx_static_check. ENDCLASS. CLASS shirt DEFINITION. PUBLIC SECTION. TYPES ty_size TYPE i. CONSTANTS: BEGIN OF gc_sizes, s TYPE ty_size VALUE 0, m TYPE ty_size VALUE 1, l TYPE ty_size VALUE 2, xl TYPE ty_size VALUE 3, END OF gc_sizes. METHODS constructor IMPORTING iv_size TYPE ty_size DEFAULT gc_sizes-s RAISING cx_wrong_size. ... PRIVATE SECTION. DATA mv_size TYPE tsize. ENDCLASS. CLASS shirt IMPLEMENTATION. METHOD constructor. IF iv_size <> gc_sizes-s AND iv_size <> gc_sizes-m AND iv_size <> gc_sizes-l AND iv_size <> gc_sizes-xl. RAISE EXCEPTION TYPE cx_wrong_size. ENDIF. me->mv_size = iv_size. ENDMETHOD. ENDCLASS. |
При создании объекта класса shirt мы обязаны указать его размер, который обязательно должен быть одним из значений объявленных в именованной группе констант gc_sizes, в противном случае мы вызовем исключение, которое необходимо обработать в клиентском коде:
1 2 3 4 5 |
TRY. DATA(shirt) = NEW shirt( shirt=>size_xl ). CATCH cx_wrong_size. ... ENDTRY. |
В данном примере есть два существенных недостатка:
- Необходим контроль на уровне API за передаваемыми значениями,
- Необходима дополнительная обработка исключений в клиентском коде.
Рассмотрим еще один пример, но уже на базе ООП:
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 |
CLASS lcl_size DEFINITION CREATE PRIVATE . PUBLIC SECTION. CLASS-DATA s TYPE REF TO lcl_size READ-ONLY . CLASS-DATA m TYPE REF TO lcl_size READ-ONLY . CLASS-DATA l TYPE REF TO lcl_size READ-ONLY . CLASS-DATA xl TYPE REF TO lcl_size READ-ONLY . DATA id TYPE string READ-ONLY . CLASS-METHODS class_constructor . PRIVATE SECTION. METHODS constructor IMPORTING !i_id TYPE string . ENDCLASS. CLASS lcl_size IMPLEMENTATION. METHOD class_constructor. lcl_size=>s = NEW #('s'). lcl_size=>m = NEW #('m'). lcl_size=>l = NEW #('l'). lcl_size=>xl = NEW #('xl'). ENDMETHOD. METHOD constructor. me->id = i_id. ENDMETHOD. ENDCLASS. CLASS shirt DEFINITION. PUBLIC SECTION. METHODS constructor IMPORTING io_size TYPE REF TO lcl_size. ... PRIVATE SECTION. DATA mo_size TYPE REF TO lcl_size. ENDCLASS. CLASS shirt IMPLEMENTATION. METHOD constructor. me->mo_size = io_size. ENDMETHOD. ENDCLASS. START-OF-SELECTION. DATA lo_shirt TYPE REF TO shirt. lo_shirt = NEW #( lcl_size=>s ). |
Данный пример лишён предыдущих недостатков, т.к. создавать инстанции класса может только он сам (CREATE PRIVATE), в клиентском коде не требуется доп. контроля. Однако так или иначе требуется дополнительное кодирование, которое в случае встроенных перечисляемых типов можно избежать.
Давайте перепишем наш пример с использованием определения перечисляемого типа:
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 |
CLASS shirt DEFINITION. PUBLIC SECTION. TYPES: BEGIN OF ENUM ty_size, s, m, l, xl, END OF ENUM ty_size. METHODS constructor IMPORTING iv_size TYPE ty_size OPTIONAL. ... PRIVATE SECTION. DATA mv_size TYPE ty_size. ENDCLASS. CLASS shirt IMPLEMENTATION. METHOD constructor. IF iv_size IS NOT INITIAL. me->mv_size = iv_size. ELSE. me->mv_size = xl. ENDIF. ENDMETHOD. ENDCLASS. START-OF-SELECTION. DATA(lo_shirt) = NEW shirt( shirt=>xl ). |
Как видно кода стало заметно меньше. Перечисляемый тип создаётся с помощью ключевых слов BEGIN OF ENUM.. END OF ENUM. Используемый по умолчанию тип данных для компонентов перечисляемого типа — I (целое), причем значения начинаются с 0. Т.е. конструкцию из примера можно было бы заменить на такую (с явным указанием базового типа):
1 2 3 4 5 6 7 |
TYPES: BEGIN OF ENUM ty_size BASE TYPE i, s VALUE IS INITIAL, m VALUE 1, l VALUE 2, xl VALUE 3, END OF ENUM ty_size. |
При явном указании типа в каждом перечислении должно быть явно объявлено INITIAL значение. Сделана такая проверка для возможности использования конструкции IS INITIAL к переменным перечисляемого типа. Указание базового типа позволяет выполнить рефакторинг существующих до 7.51 перечислений к новому синтаксису, однако следует учитывать ряд ограничений:
- Можно использовать все числовые типы, типы даты и времени,
- Символьные типы не могут иметь значения более 8 символов,
- Байтовые не более 16,
- Нельзя использовать string и xstring.
Обратите внимание на конструктор, отдельные значения из перечисления можно использовать без указания типа:
1 |
me->mv_size = xl. |
Однако если в рамках текущей области видимости объявляются два типа перечислений с одинаковыми именами, возникнет ошибка:
1 2 3 4 5 6 7 8 9 10 11 12 |
TYPES: BEGIN OF ENUM ty_size BASE TYPE i, s VALUE IS INITIAL, m VALUE 1, l VALUE 2, xl VALUE 3, END OF ENUM ty_size, BEGIN OF ENUM ty_diff_size BASE TYPE i, s VALUE IS INITIAL, " Ошибка - s уже было объявлено в типе ty_size m VALUE 1, END OF ENUM ty_diff_size. |
Чтобы такого не происходило, используется дополнение STRUCTURE:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
... TYPES: BEGIN OF ENUM ty_size STRUCTURE size_one BASE TYPE i, s VALUE IS INITIAL, m VALUE 1, l VALUE 2, xl VALUE 3, END OF ENUM ty_size STRUCTURE size_one, BEGIN OF ENUM ty_diff_size STRUCTURE size_two BASE TYPE i, s VALUE IS INITIAL, m VALUE 1, END OF ENUM ty_diff_size STRUCTURE size_two. ... METHOD constructor. IF iv_size IS NOT INITIAL. me->mv_size = iv_size. ELSE. me->mv_size = size_one-s. " Ошибки не возникнет ENDIF. ... DATA(lo_shirt) = NEW shirt( shirt=>size_one-s ). |
В текущем виде будет создано две структуры, компоненты которой имеют технический тип перечисления — ENUM, а имена компонентов соответствуют именам компонента перечисляемого типа.
Несмотря на то, что компоненты перечисляемого типа имеют заданный базовый тип, напрямую передавать значения такого типа (вместо компонентов) уже нельзя:
1 2 |
START-OF-SELECTION. DATA(lo_shirt) = NEW shirt( 3 ). " Ошибка синтаксиса |
Однако можно воспользоваться явной конвертацией к типу:
1 |
DATA(lo_shirt) = NEW shirt( CONV shirt=>ty_size( 3 ) ). " Без ошибки |
Если конвертация не может быть выполнена, возникнет исключение CX_SY_CONVERSION_NO_ENUM_VALUE.
Использовать компоненты перечислений напрямую, например в формуле, так же не выйдет:
1 2 |
DATA lv_i TYPE i. lv_i = shirt=>xl * 10. |
Но явная конвертация к типу может нам помочь:
1 2 |
DATA lv_i TYPE i. lv_i = CONV i( shirt=>xl ) * 10. |
Результат: 30, т.к. базовый тип перечисляемого типа в данном случае это i, начинается с 0, значение для XL = 3.
Более подробно о правилах явной конвертации в перечислениях можно ознакомиться в документации.
В целом перечисления можно присваивать только переменным с типом заданного перечисления, однако существует исключение из этого правила, при присвоении к символьным переменным:
1 2 3 4 |
DATA lv_string TYPE string. lv_string = shirt=>size_one-s. " Значение переменной будет именем " компонента перечисляемого типа в " верхнем регистре - S. |
Система описания типов RTTI так же была расширена на специальный класс для работы с перечисляемыми типами:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
TYPES: BEGIN OF ENUM tsize, size_s, size_m, size_l, size_xl, END OF ENUM tsize. DATA(size) = VALUE tsize( ). DATA(enum_descr) = CAST cl_abap_enumdescr( cl_abap_typedescr=>describe_by_data( size ) ). cl_demo_output=>new( )->write_data( enum_descr->kind "E, for elementary )->write_data( enum_descr->type_kind "k, new for enumerated type )->write_data( enum_descr->base_type_kind "I, the base type )->write_data( enum_descr->members "Table of constants and values )->display( ). |