
To represent a fixed value set in ABAP you can use several different technologies. The newest one is enumerations which are provided at language level and can be used as of AS ABAP 7.51. With this blog post I want to show which possibilities there are for ABAP developers to define enumerations and use them in signature elements in the safest way possible.
CLASS lcl_light_switcher DEFINITION.
PUBLIC SECTION.
CLASS-METHODS:
"! Switch the lights on / off
"! @parameter iv_on | Should they be on?
switch IMPORTING iv_on TYPE abap_bool.
ENDCLASS.
iv_on
should either be ' '
or 'X'
because the type of the parameter is abap_bool
. So typical usage would be something like this (preferred using the abap_* constants):lcl_light_switcher=>switch( abap_true ).
lcl_light_switcher=>switch( abap_false ).
lcl_light_switcher=>switch( ' ' ).
lcl_light_switcher=>switch( space ).
lcl_light_switcher=>switch( 'X' ).
' '
or 'X'
because abap_bool
is just a type definition in the global type-pool abap and its actual type is C with a length of 1.lcl_light_switcher=>switch( 'A' ).
' '
and 'X'
should be used. For abap_bool
this is probably fine because most developers know about abap's missing in-build boolean type and the ways around it. But what about other types that are module / application specific or that you define on your own?abap_true
and abap_false
) we have to define them on our own. But where do we do that? There are three different types of development objects to use:INTERFACE zif_light_states PUBLIC.
TYPES:
gty_light_state TYPE i.
CONSTANTS:
gc_green TYPE gty_light_state VALUE 0,
gc_yellow TYPE gty_light_state VALUE 1,
gc_red TYPE gty_light_state VALUE 2,
gc_red_and_yellow TYPE gty_light_state VALUE 3.
ENDINTERFACE.
CLASS zcl_light_switcher DEFINITION
PUBLIC
CREATE PUBLIC.
PUBLIC SECTION.
METHODS:
"! Switch the light
"! @parameter iv_new_state | The new light color (see ZIF_LIGHT_STATES constants)
"! @raising zcx_illegal_argument | iv_new_state is invalid
switch_light IMPORTING iv_new_state TYPE zif_light_states=>gty_light_state
RAISING zcx_illegal_argument.
PROTECTED SECTION.
DATA:
mv_light TYPE zif_light_states=>gty_light_state.
PRIVATE SECTION.
ENDCLASS.
CLASS zcl_light_switcher IMPLEMENTATION.
METHOD switch_light.
TYPES: lty_i_range TYPE RANGE OF zif_light_states=>gty_light_state.
" Validate iv_new_state
IF iv_new_state NOT IN VALUE lty_i_range(
( sign = 'I' option = 'EQ' low = zif_light_states=>gc_green )
( sign = 'I' option = 'EQ' low = zif_light_states=>gc_yellow )
( sign = 'I' option = 'EQ' low = zif_light_states=>gc_red )
( sign = 'I' option = 'EQ' low = zif_light_states=>gc_red_and_yellow )
).
RAISE EXCEPTION TYPE zcx_illegal_argument
EXPORTING
is_message = zcx_illegal_argument=>gc_with_name_and_value
iv_par_name = 'IV_NEW_STATE'
iv_value = iv_new_state ##NO_TEXT.
ENDIF.
mv_light = iv_new_state.
ENDMETHOD.
ENDCLASS.
gty_light_state
in the interface would normally be unnecessary (because the inbuilt type I of course already exists) but it helps to reference the place where the constants are stored. You could of course also create a DDIC data element and refer to the interface in its description. But if it is not documented somewhere, the API user is forced to look into the implementation of the method to find out what actual parameters to use (which is troublesome and not always possible, think of defining your method in an interface without implementation).CLASS zcl_light_switcher DEFINITION
PUBLIC
CREATE PUBLIC.
PUBLIC SECTION.
METHODS:
"! Switch the light
"! @parameter iv_new_state | The new light color (see ZSCN_D_LIGHTSTATE domain)
"! @raising zcx_illegal_argument | iv_new_state is invalid
switch_light IMPORTING iv_new_state TYPE zscn_l_lightstate
RAISING zcx_illegal_argument.
PROTECTED SECTION.
DATA:
mv_light TYPE zscn_l_lightstate.
PRIVATE SECTION.
ENDCLASS.
CLASS zcl_light_switcher IMPLEMENTATION.
METHOD switch_light.
" Validate iv_new_state
CALL FUNCTION 'CHECK_DOMAIN_VALUES'
EXPORTING
domname = 'ZSCN_D_LIGHTSTATE'
value = iv_new_state.
IF sy-subrc <> 0.
RAISE EXCEPTION TYPE zcx_illegal_argument
EXPORTING
is_message = zcx_illegal_argument=>gc_not_in_domain
iv_par_name = 'IV_NEW_STATE'
iv_value = iv_new_state
iv_domname = 'ZSCN_D_LIGHTSTATE' ##NO_TEXT.
ENDIF.
mv_light = iv_new_state.
ENDMETHOD.
ENDCLASS.
CLASS zcl_light_switcher DEFINITION
PUBLIC
CREATE PUBLIC.
PUBLIC SECTION.
TYPES:
BEGIN OF ENUM gte_light_state,
green,
yellow,
red,
red_and_yellow,
END OF ENUM gte_light_state.
METHODS:
switch_light IMPORTING ie_new_state TYPE gte_light_state.
(...)
ENDCLASS.
package com.flaiker.scnenums;
public enum LightStates {
GREEN, YELLOW, RED, RED_AND_YELLOW;
}
package com.flaiker.scnenums;
public enum LightStates {
GREEN("Green"),
YELLOW("Yellow"),
RED("Red"),
RED_AND_YELLOW("Red and yellow");
private String name;
private LightStates(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
CLASS zcl_light_state DEFINITION
PUBLIC
FINAL
CREATE PRIVATE.
PUBLIC SECTION.
CLASS-METHODS:
class_constructor.
CLASS-DATA:
go_green TYPE REF TO zcl_light_state READ-ONLY,
go_yellow TYPE REF TO zcl_light_state READ-ONLY,
go_red TYPE REF TO zcl_light_state READ-ONLY,
go_red_and_yellow TYPE REF TO zcl_light_state READ-ONLY.
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.
CLASS zcl_light_state IMPLEMENTATION.
METHOD class_constructor.
CREATE OBJECT: go_green, go_yellow, go_red, go_red_and_yellow.
ENDMETHOD.
ENDCLASS.
READ-ONLY
and effectively final). They are loaded on first use of the class using the class constructor and refer to instances of the class itself. It is not possible to create any other instances of the class because the constructor is private (CREATE PRIVATE
) and the class is also final (so no publicly creatable subclasses). This means there can only ever be these 4 instances of the class available (*). You can now use the enumeration class similarly to the constants interface but except for comparing constant values now memory addresses will be compared.CLASS zcl_light_switcher DEFINITION
PUBLIC
CREATE PUBLIC.
PUBLIC SECTION.
METHODS:
"! Switch the light
"! @parameter io_new_state | The new light color
"! @raising zcx_illegal_argument | io_new_state cannot be null
switch_light IMPORTING io_new_state TYPE REF TO zcl_light_state
RAISING zcx_illegal_argument,
"! @parameter ro_state | Current light state
get_current_state RETURNING VALUE(ro_state) TYPE REF TO zcl_light_state.
PROTECTED SECTION.
DATA:
mo_light TYPE REF TO zcl_light_state.
PRIVATE SECTION.
ENDCLASS.
CLASS zcl_light_switcher IMPLEMENTATION.
METHOD switch_light.
" Validate io_new_state
IF io_new_state IS NOT BOUND.
RAISE EXCEPTION TYPE zcx_illegal_argument
EXPORTING
is_message = zcx_illegal_argument=>gc_nullpointer
iv_par_name = 'IO_NEW_STATE' ##NO_TEXT.
ENDIF.
mo_light = io_new_state.
ENDMETHOD.
METHOD get_current_state.
ro_state = mo_light.
ENDMETHOD.
ENDCLASS.
switch_light
method it is clear that io_new_state
must be an instance of zcl_light_state because of the type of the parameter. He can therefore (knowing of class based enumerations) look for the available values in the class-data of the class and use one of the values as the actual parameter, like this:lo_light_switcher->switch_light( zcl_light_states=>go_green ).
"! Enumeration to represent states of traffic lights
CLASS zcl_light_state DEFINITION
PUBLIC
FINAL
CREATE PRIVATE.
PUBLIC SECTION.
CLASS-METHODS:
class_constructor,
"! Get enum instance from name
"! @parameter iv_name | Name of the enum value
"! @parameter ro_light_state | Found enum value
"! @raising zcx_illegal_argument | iv_name is not the name of any enum value
from_name IMPORTING iv_name TYPE string
RETURNING VALUE(ro_light_state) TYPE REF TO zcl_light_state
RAISING zcx_illegal_argument.
METHODS:
"! Get the next state
"! @parameter ro_next | Next state
get_next RETURNING VALUE(ro_next) TYPE REF TO zcl_light_state.
CLASS-DATA:
"! Traffic can and should flow
go_green TYPE REF TO zcl_light_state READ-ONLY,
"! Traffic can flow but soon cannot
go_yellow TYPE REF TO zcl_light_state READ-ONLY,
"! Traffic must not flow
go_red TYPE REF TO zcl_light_state READ-ONLY,
"! Traffic must not flow but can soon
go_red_and_yellow TYPE REF TO zcl_light_state READ-ONLY.
DATA:
mv_name TYPE string READ-ONLY.
PROTECTED SECTION.
PRIVATE SECTION.
METHODS:
"! @parameter iv_name | Name of the light state, must be unique!
constructor IMPORTING iv_name TYPE csequence.
CLASS-DATA:
gt_registry TYPE STANDARD TABLE OF REF TO zcl_light_state.
ENDCLASS.
CLASS zcl_light_state IMPLEMENTATION.
METHOD class_constructor.
DEFINE init.
&1 = NEW #( &2 ).
INSERT &1 INTO TABLE gt_registry.
END-OF-DEFINITION.
init: go_green `Green`,
go_yellow `Yellow`,
go_red `Red`,
go_red_and_yellow `Red + Yellow`.
ENDMETHOD.
METHOD from_name.
TRY.
ro_light_state = gt_registry[ table_line->mv_name = iv_name ].
CATCH cx_sy_itab_line_not_found INTO DATA(lx_ex).
RAISE EXCEPTION TYPE zcx_illegal_argument
EXPORTING
is_message = zcx_illegal_argument=>gc_enum_not_registered
iv_par_name = 'IV_NAME'
iv_value = iv_name
ix_previous = lx_ex ##NO_TEXT.
ENDTRY.
ENDMETHOD.
METHOD constructor.
mv_name = iv_name.
ENDMETHOD.
METHOD get_next.
ro_next = SWITCH #( me WHEN go_green THEN go_yellow
WHEN go_yellow THEN go_red
WHEN go_red THEN go_red_and_yellow
WHEN go_red_and_yellow THEN go_green ).
" If this fails a new enum value has been added and is not yet supported
" by this method.
ASSERT ro_next IS BOUND.
ENDMETHOD.
ENDCLASS.
lo_light_switcher->switch( lo_light_switcher->get_current_state( )->get_next( ) ).
Technology | Dynpro support | Static access to values | Values can be added without needing to adjust validation logic | DB persistence | Enhancable using additional methods / attributes |
Constants (in Type-Pools, Interfaces, Classes) | No | Yes | No | Yes (using constant) | No |
Domains | Yes | No | Yes | Yes (using domain key) | No |
Enumerations (7.51) | No | Yes | Yes (check for INITIAL needed and maybe more? *2) | Yes (using auto assigned constant) | No |
OO enumeration classes | No | Yes | Yes (only nullpointer validation needed) | Serialization logic must be implemented | Yes (full OO class) |
CLASS zcl_light_state DEFINITION
PUBLIC
FINAL
CREATE PRIVATE.
PUBLIC SECTION.
TYPES:
BEGIN OF ENUM gte_light_state,
green,
yellow,
red,
red_and_yellow,
END OF ENUM gte_light_state.
CLASS-METHODS:
get IMPORTING ie_enum TYPE gte_light_state
RETURNING VALUE(ro_light_state) TYPE REF TO zcl_light_state.
METHODS:
get_next RETURNING VALUE(ro_next) TYPE REF TO zcl_light_state.
DATA:
mv_name TYPE string READ-ONLY,
me_enum TYPE gte_light_state READ-ONLY.
PROTECTED SECTION.
PRIVATE SECTION.
TYPES:
BEGIN OF gty_registry,
enum TYPE gte_light_state,
instance TYPE REF TO zcl_light_state,
END OF gty_registry.
METHODS:
constructor IMPORTING iv_name TYPE csequence
ie_enum TYPE gte_light_state.
CLASS-DATA:
gt_registry TYPE SORTED TABLE OF gty_registry WITH UNIQUE KEY enum.
ENDCLASS.
CLASS zcl_light_state IMPLEMENTATION.
METHOD class_constructor.
DEFINE init.
NEW #( iv_name = &1 ie_enum = &2 ).
END-OF-DEFINITION.
init: `Green` gte_light_state-green,
`Yellow` gte_light_state-yellow,
`Red` gte_light_state-red,
`Red + Yellow` gte_light_state-red_and_yellow.
ENDMETHOD.
METHOD get.
TRY.
ro_light_state = gt_registry[ enum = ie_enum ].
CATCH cx_sy_itab_line_not_found INTO DATA(lx_ex) ##NEEDED.
ASSERT 1 = 2.
ENDTRY.
ENDMETHOD.
METHOD constructor.
mv_name = iv_name.
me_enum = ie_enum.
INSERT VALUE #( instance = me enum = ie_enum ) INTO TABLE gt_registry.
ASSERT sy-subrc = 0.
ENDMETHOD.
METHOD get_next.
ro_next = SWITCH #(
me->me_enum
WHEN gte_light_state-green THEN get( gte_light_state-yellow )
WHEN gte_light_state-yellow THEN get( gte_light_state-red )
WHEN gte_light_state-red THEN get( gte_light_state-red_and_yellow )
WHEN gte_light_state-red_and_yellow THEN get( gte_light_state-green )
).
ASSERT ro_next IS BOUND.
ENDMETHOD.
ENDCLASS.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
User | Count |
---|---|
3 | |
2 | |
2 | |
2 | |
1 | |
1 | |
1 | |
1 | |
1 | |
1 |