Skip to Content
Technical Articles
Author's profile photo Jörg Krause

ABAP-Types with class

An application has its types: table types, structures, even types for references to classes may be useful. In order to make types accessible to all classes of our application, it’s obvious that they should go to a central point, which could be a class or an interface.

I personally prefer a class for types, because an interface has a different usage: it is made for being implemented by another class. Types are only declarations that should be available from different classes. But all what I am going to talk about works for interfaces and classes as well.

So we start our application in our brand new packet, let’s name it ZAPP.

The types class

class zcl_app_types definition.
  public section.
    types awesome_data_line type some_structure_from_ddic.
    types awesome_data_lines type standard table of awesome_data_line with empty key.
endclass.

Now we are ready to use it:

class zcl_app_backend definition.
  public section.
    methods do_the_magic_on_table
     importing data_line type zcl_app_types=>awesome_data_lines.
    
    methods do_the_magic_on_line
      importing data_line type zcl_app_types=>awesome_data_line.

  private section.
    data awesome_data_lines type zcl_app_types=>awesome_data_lines.
endclass.

Use a shorter alias for the type class

But how can we make it slimmer? It’s pretty noisy having always the ZCL_APP_TYPES as a prefix for each type. In real live, the name may be much longer (real live example: ZCL_MM00_PURCH_HISTORY_TYPES). With interfaces, we could use aliases to map types to local names. But this means creating a new alias for each new type of the local class which is quite noisy too.

With a type class, we can do a mapping with a simple trick: creating a data object with reference to the class. The data object offers all public static elements such as types. Using this, the class transforms like this:

class zcl_app_backend definition.

public section.
  data types type ref to ZCL_APP_TYPES.

  methods do_the_magic_on_table
    importing data_line type types->awesome_data_lines.

  methods do_the_magic_on_line
    importing data_line type types->awesome_data_line.

private section.
  data awesome_data_lines type types->awesome_data_lines.
endclass.

 

Types from other APIs

Now we have types-> as a prefix for all types of out application-own types class. When we use types from other API applications, the name extends a bit:

data common_types type ref to zcl_abap_common_types.
...
methods get_binary_lines
  returning(result) type common_types->ty_binary_lines.

 

When use types classes

I personally keep all types that are used in more than one class in my types class. In other words: no public types if not in the type class. However, there are always exceptions to the rule. In DB_ACCESS classes that are intended to only transfer data from the data base to the application, I sometimes define my result table directly in the interface:

interface zif_app_db_access.

  types settings type standard table of zdb_pp01_001 with empty key.

  methods read_settings
    returning(result) type settings.

This is OK for me as long as I never see

type zif_app_db_access=>settings

in my code. As soon a the type is needed in other classes than the DB_ACCESS class, I would transfer it to zcl_app_types.

Where is the benefit?

To me, it is very simple to just use types-> to have access to the types that my app is using. Using the editor’s code completion I can see directly every type already available. Since every type is in one class, no ambiguous names occur. (think of zif_db_access=>ty_data, zcl_app_backend=>ty_data…). Distinguishing between types and variables is also easier for the types look always like type->data_lines while the variable appears as data_lines only.

The shorter naming extends also legibility because code lines are kept shorter. Each set of types can have a clear name that replaces the global name of the class.

Assigned Tags

      27 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo André Brito
      André Brito

      Hi Jörg,

      I personally use a lot the types declaration within a class but I have to disagree with you on one point: "When use types classes - I personally keep all types that are used in more than one class in my types class".

      If you need to use the same type in more than one place, I would always suggest creating it in SE11.

       

      Cheers,

      André

       

       

      Author's profile photo Jörg Krause
      Jörg Krause
      Blog Post Author

      To me, a DDIC type is useful when using it in user interaction. Otherwise defining texts for labels in different sizes and languages seems needless to me. I prefer to define types that serve for program logic and not for user interaction directly in ABAP code. This is also less time-consuming.

      Author's profile photo Jelena Perfiljeva
      Jelena Perfiljeva

      I agree with this philosophy. If it's a purely "technical" type, i.e. we don't need any semantic definitions and don't plan taking advantage of anything associated with dictionary types (i.e. it won't be anything user-facing or used in a service definition that can be generated, and such) then there seems to be no advantage in using dictionary. Granted, SE11 has better "discoverability" but it also means it can easily become polluted, especially when similarly named elements are used in different contexts with different definitions.

      It's all about the use case, plain put.

      Thanks for the blog!

      Author's profile photo Oleg Bashkatov
      Oleg Bashkatov

      what is the difference between DDIC-structure and public type of interface?

      in both cases it is in server memory, but in DDIC it is more manageable and clear to check and compare.

      Author's profile photo Sandra Rossi
      Sandra Rossi

      I guess one argument in favor of defining a type in the DDIC is to avoid having errors during the transport of repository objects, by transporting the DDIC objects first in a dedicated request (as far as a DDIC type does not refer to a class or an interface...)

      But there is the Transport Check tool to avoid those errors too...

      Author's profile photo Michael Keller
      Michael Keller

      As a note: If a class isn't a type class and you discover a lot of types statements in it, then this can be a hint that the class has too many tasks (see "Separation of concerns").

      Author's profile photo Oleg Bashkatov
      Oleg Bashkatov

      I think for NetWeaver is not the best option to use types in class. DDIC is the more preferable.

      The main reason: when the types in class or interface is changing , the program where it is used is  forced to regenerate and it could cause the syntax errors (LOAD_PROGRAMM_MISMATCH) during the transporting.

      Another option is DTO (but it is differ from types).

      Author's profile photo Sandra Rossi
      Sandra Rossi

      What is DTO?

      Author's profile photo Oleg Bashkatov
      Oleg Bashkatov

      DTO on wiki

       

      Data Transfer Object sample:

      CLASS zcl_order_conf_dto DEFINITION
        PUBLIC
        FINAL
        CREATE PUBLIC .
      
        PUBLIC SECTION.
          DATA mv_kunnr TYPE kunnr READ-ONLY.
      
          METHODS set_customer
            IMPORTING iv TYPE kunnr.
      
          METHODS set_any_other_field
            IMPORTING ivr TYPE REF TO char10.
      
          METHODS get_any_other_field
            RETURNING VALUE(rv) TYPE REF TO char10.
      
        PROTECTED SECTION.
        PRIVATE SECTION.
          DATA mv_any_other_field TYPE REF TO char10.
      
      ENDCLASS.
      
      
      
      CLASS zcl_order_conf_dto IMPLEMENTATION.
        METHOD set_customer.
          mv_kunnr = iv.
        ENDMETHOD.
      
        METHOD set_any_other_field.
          mv_any_other_field = ivr.
        ENDMETHOD.
      
        METHOD get_any_other_field.
          rv = mv_any_other_field.
        ENDMETHOD.
      
      ENDCLASS.

       

       

       

      Author's profile photo Jörg Krause
      Jörg Krause
      Blog Post Author

      Sincerely I do not get your point Wouldn't a change of a ddic type also cause a regeneration of the program? Whether you have types in DDIC or a class, the repo objects must be always aligned  before running the program.

      In case of a type class with 100 types you have one repo object to align. When each type is in ddic you have 100 repo objects to align. I prefer the first one.

      Author's profile photo Oleg Bashkatov
      Oleg Bashkatov

      in case class will be used in several reports/classes/functions - all this functions would be regenerated even if you change .

      in case you have 100 separate objects - small change will cause regeneration only where exactly it  one changed object is used.

      For global types abap_help mostly tells us that declaration is up to context, but provide recommendation about separation of concerns and semantics meaning.

      ABAP declaration

       

      Author's profile photo Jörg Krause
      Jörg Krause
      Blog Post Author

      I understand the point. But I do not see the disadvantage - when a change comes with transport order, why should i be concerned whether 10 or 40 programs are regenerated?

      As described above, the type classes are application-exclusive. So the where-used lists of them are not too big.

      Another thing is the name: a ddic type can have a 30 character name. Depending on your naming conventions (or even worse a name space) few space may remain for a clear and descriptive name.

      Having the type in a class, you can use all of the 30 characters available to the name. Since the type class is application-exclusive, no prefixes or namespaces are needed.

       

      Author's profile photo Oleg Bashkatov
      Oleg Bashkatov

      But I do not see the disadvantage – when a change comes with transport order, why should i be concerned whether 10 or 40 programs are regenerated?

      yes, you are correct. it depends on the situation. And sometimes we should not care about this. Agree. But sometimes we should.

      Having the type in a class, you can use all of the 30 characters available to the name. Since the type class is application-exclusive, no prefixes or namespaces are needed.

      ? However in DDIC I could put also documentation and make reference to data element.

       

      PS. Actually I am using both types in class/interface and DDIC and agree with you point but trying to provide another approach.

      Mostly I am using types in class + DTO and it provides more semantic information for code understanding.

      Author's profile photo Jörg Krause
      Jörg Krause
      Blog Post Author

      ? However in DDIC I could put also documentation and make reference to data element.

      For a reader of my code, the documentation is several clicks away.

      Say, we have an application in a package ZMM01_MRP_UTIL. Using a types class, I can name a needed type freely

      methods read_storage_loc_mrp_parms
        importing number type matnr
        returning value(result) type types->storage_loc_mrp_parameters.

      Using a DD type, I have to take care of the name in order to avoid conflicts with other application. It could lead to a coding like this:

      methods read_storage_loc_mrp_parms
        importing number type matnr
        returning value(result) type /xxx/st_mm01_mrp_util_stl_prms.

      Personally, I find the first example better to read.

      References to data elements can be made in type declarations too.

       

      Author's profile photo Oleg Bashkatov
      Oleg Bashkatov

      Personally, I find the first example better to read.

       

      I think it depends on whether the method public or private.

      In case you have incapsulated logic (or group of class) - it would be very nice to have type in interface or class as you specified.

      But in case you have many-points to use type (structure or data element) - it would be more clear to use DDIC-structure.

      Another score for DDIC is parameters in RFC/UPDATE TASKs and OData-services. It is impossible there to use interfaces types. and in that case where used list would show that object is part of some integration. in case of interface type you would not find it.

      Author's profile photo Andrea Borgia
      Andrea Borgia

      Lars Hvam what are your thoughts on this? Especially the part on public types: "no public types if not in the type class", is it an approach we could adopt?

      Author's profile photo Lars Hvam
      Lars Hvam

      I have not read the full blog, but it seems like a anti-pattern, the types class will become a god object

      Define the types in the interfaces where they belong with the logic/abstraction, define types in ddic if its used in databases or dynpros(for texts)

      Author's profile photo Jörg Krause
      Jörg Krause
      Blog Post Author

      I am creating own types classes for each application. Why should these have the smell of a god class?

      Author's profile photo Lars Hvam
      Lars Hvam

      If an application is small, it will not matter, but applications tend to grow over time.

      Take abapGit as an example, would it be considered an application? It has around 530 TYPES definitions.

      Author's profile photo Jörg Krause
      Jörg Krause
      Blog Post Author

      It's true that one should not have one class with 500 types in it. As applications grow, things should be drilled down to smaller entities. So do types classes. Typically, I add sub packages when applications grow big. so I can divide also my types classes.

      Author's profile photo Angel Sabogal
      Angel Sabogal

      This code throws error:

      class zcl_app_backend definition.
      
      public section.
        data types type ref to ZCL_APP_TYPES.
      
        methods do_the_magic_on_table
          importing data_line type types->awesome_data_lines.
      ...

      That's because the object "types" is not instantiated in the defintion section. I don't know whether such issue depends on de abap version (I'm using 7.4 sp6)

      Anyway I've used this method in the implementation section but only after starting the "types" object in the constructor:

        METHOD constructor.
          super->constructor( ).
          me->types = NEW #( ).
        ....
        ENDMETHOD.
      

       

      I found that having a class as a types and constants container has some nice advantages if you need some semi-constant data. For instance:

      CLASS ZCL_MY_TYPES IMPLEMENTATION.
      
        METHOD constructor.    
          "t_some_static_range is defined in the public section
          me->t_some_static_range = value #(
            ( sign = 'I' option = 'EQ' low = 'some_static_value_1' ) 
            ( sign = 'I' option = 'EQ' low = 'some_static_value_2' )
          ).
        ENDMETHOD.
      
      ENDCLASS.
      
      * Then create the object:
      
      data(o_types) = new zcl_my_types( ).
      
      * And use it in any place:
      
      if v_some_vble in o_types->t_some_static_range.
         ...
      endif.
      
      
      
      

       

       

      Author's profile photo Jörg Krause
      Jörg Krause
      Blog Post Author

      I am on 750 SP 19 - here it works.

      However - meanwhile I found out that generating the test frame for classes (when pressing F8 from the editor) does not work with declarations like this. It seems that ABAP (in my version) is able to access the types of a non-instantiated object variable, but the code generator is not able to handle it.

      Therefore, I switched to creating the types class as abstract and derive a local class-relevant class from it:

      class zcl_sd37_billing_print_types definition
        public
        abstract
        create public .
      
        public section.
          types: ....
      
      
      *(class-relevant local types of consumer class)
      
      class types definition inheriting from zcl_sd37_billing_print_types final.
      endclass.
      
      *(class coding)
        public section.
          methods read
            importing linked_orderid type zcl_sd37_billing_print_types=>linked_orderid
      
        private section.
          data linked_orderid type types=>linked_orderid.
      
      
      
      

      Note that with this approach (as shown abovd), you will have to use the full class name in the public section

      Author's profile photo Sergiu Iatco
      Sergiu Iatco

      How to reference to a parameter of a method when the type is not defined in the class?

       

      class zcl_app definition.
        public section.
          methods do_the_magic_on_table
            importing data_lines type standard table of mara with empty key.

       

      Something like this:

      data: data_lines_from_method like zcl_app=>do_the_magic_on_table=>data_lines.
      

       

      Author's profile photo Jörg Krause
      Jörg Krause
      Blog Post Author

      Why should you abuse a parameter definition as a type defintion? Define your type in the types class:

       

      class zcl_app_types.
      
        public section
          types mara_Lines type standard table of mara with empty key.

      then refer to the type from both the method definition and the data line.

      Author's profile photo Jascha Schroeter
      Jascha Schroeter

      I use the same approach, but I am facing a problem on how to correctly determine the domain at runtime of a type that uses your kind of "alias".

      Say, I have a class y_owner with one type and a setter method with an input parameter of that class type.

      class y_owner definition
        public
        create public .
      
        public section.
      
          types:
            type type wcb_owner_type.
      
          methods:
            set_type
              importing iv_type type y_owner=>type.

      WCB_OWNER_TYPE is a domain with fixed values. I want to write a unit test for setting valid values, and for that I want to get all fixed domain values of "type" (WCB_OWNER_TYPE).

      Before I can do that, I need the domain name of the field, so WCB_OWNER_TYPE.

      But I can't get it through RTTI, because the absolute name is obfuscated by the class type:
      RTTI%20of%20iv_type

      RTTI of iv_type

      This also leads to the fact that it is not recognized as a DDIC type, despite it actually being one, but underneath that "class type".

      I don't want to hardcode the domain name into my tests and I also want to keep using the "class type".

      Any idea? I'm on 7.56.

      Author's profile photo Jörg Krause
      Jörg Krause
      Blog Post Author

      Hello,

      you can use the public attribute HELP_ID of the class cl_abap_typedescr to determine the domain at run time. See my example (note that I use the abstract class approach mentioned in my comment above):

      *&---------------------------------------------------------------------*
      *& Report zp_tmp_sapblog_types
      *&---------------------------------------------------------------------*
      *&
      *&---------------------------------------------------------------------*
      report zp_tmp_sapblog_types.
      
      class types definition inheriting from zcl_sapblog_types abstract.
      endclass.
      
      class app definition.
        public section.
          methods main
            returning value(result) type types=>my_vbtyp.
      
      endclass.
      
      class app implementation.
      
        method main.
          result = '>'.
        endmethod.
      
      endclass.
      
      
      class test definition for testing duration short risk level harmless.
        public section.
      
        private section.
          data cut type ref to app.
      
          methods setup.
      
          methods test_main for testing.
      endclass.
      
      CLASS test IMPLEMENTATION.
      
        METHOD setup.
          cut = new #( ).
        ENDMETHOD.
      
        METHOD test_main.
          DATA: domain_values TYPE STANDARD TABLE OF dd07v.
          data(result) = cut->main( ).
      
          data(descr) = cast cl_abap_elemdescr( cl_abap_typedescr=>describe_by_data( result ) ).
          data(dom_name) = conv ddobjname( descr->help_id ).
      
          call function 'DDUT_DOMVALUES_GET'
            exporting
              name          =  dom_name
            tables
              dd07v_tab     =  domain_values.
      
          cl_abap_unit_assert=>assert_table_contains( table = domain_values
                                                      line = result ).
        ENDMETHOD.
      
      ENDCLASS.
      Author's profile photo Jascha Schroeter
      Jascha Schroeter

      Thanks, Jörg! I had noticed the help_id, but could not be certain that it always contains the domain.