Skip to Content
Technical Articles
Author's profile photo Jan-Willem Kaagman

Design Patterns in ABAP: Factory and singleton (and the wonderous world of OO-Abap)

Introduction

Did you ever encounter that even after the statement DEFINITION DEFERRED you still need to move the definition of your class? Or have you ever wondered that if a singleton-class has several subclasses, why all those subclasses will be singletons, or that all classes in the tree will be one singleton? Those experiences were new to me, but I encountered them and I wanted to share my amazement.

This blog is a mixture of a how-to create a factory-class for singletons with inheritance, some surprises I found during coding and a request for advice. So please, please comment and ask questions. I would love to learn from my mistakes and wrong assumptions. I chose to not use test-driven approach to keep this blog more readable.

I was busy with some experimenting (hoping to write a blog about that later), and having re-read parts of the book “Design Patterns in ABAP Objects” by Kerem Koseoglu  I wanted to play more with these patterns. I was comparing several approaches and solutions to a problem, and wanted to have the same call to the different solutions (the polymorphism of object oriented development). As I was using operations I opted for using singletons, inheriting some standard behaviour from the superclass.

The road to a solution to test several different solutions for the same problems using inheritance and a factory class was not a straight road – not at all. It was such a bumpy ride that I thought this was worthy of its own blog. This blog first appeared at our own website.

The challenge

We want to perform an operation on something. In my case, I wanted to test several solutions for a certain operation on an object. So I need:

  • Uniformity in calling the solution.
  • Different reactions for the operation.
  • Managed instantiation.

An additional “restraint” is that this is a proof of concept. I didn’t want to clog the system with dozens of artifacts, so I created it in one program. This led to one interesting issue…

Interface and inheritance – Creating the Singleton(s)

First, I want to have uniformity in calling the solution. So I define an interface that states what operation should be used:

INTERFACE: zlif_interface.
  METHODS: do_something RETURNING VALUE(zrv_text) TYPE string.
ENDINTERFACE.

This is the most important part of the whole solution, and now it’s done. Almost finished!

Let us think now about the other parts. It would be nice if we can re-use some standard code, and only have few differences. That can be solved by creating a superclass, with several subclasses. The polymorphism of the subclasses will take care of the different behaviour.

CLASS zlcl_super DEFINITION ABSTRACT CREATE PROTECTED.
  PUBLIC SECTION.
    INTERFACES zlif_interface.
    ALIASES: do_something FOR zlif_interface~do_something.
ENDCLASS.

CLASS zlcl_subclass_one DEFINITION INHERITING FROM zlcl_super CREATE PROTECTED.
  PUBLIC SECTION.
    METHODS: do_something   REDEFINITION.
ENDCLASS.

CLASS zlcl_subclass_two DEFINITION INHERITING FROM zlcl_super CREATE PROTECTED.
  PUBLIC SECTION.
    METHODS: do_something   REDEFINITION.
ENDCLASS.

I decided to omit the IMPLEMENTATION part here for better readability.

The UML diagram looks like this:

UML%3A%20Singleton%20with%20Inheritance

UML: Singleton with Inheritance

As we will use these classes for operations on other objects, I want to use them as a singleton, adding a static method get_instance( ) and define a static singleton reference in the interface. I mark the classes as CREATE PROTECTED.

INTERFACE: zlif_interface.
  METHODS: do_something RETURNING VALUE(zrv_text) TYPE string.
  CLASS-DATA:    zgr_instance TYPE REF TO zlif_interface.
  CLASS-METHODS: get_instance RETURNING VALUE(zrv_instance) TYPE REF TO zlif_interface.
ENDINTERFACE.

CLASS zlcl_super DEFINITION ABSTRACT CREATE PROTECTED.
  PUBLIC SECTION.
    INTERFACES zlif_interface.
ENDCLASS.

CLASS zlcl_super IMPLEMENTATION.
  METHOD do_something.
    ...
  ENDMETHOD.
  METHOD get_instance.
    IF zgr_instance IS INITIAL.
      zgr_instance = NEW #( ).
    ENDIF.
    zrv_instance = zgr_instance. 
  ENDMETHOD.
ENDCLASS.

So far so good, right? Not really!

Issue #01: I would like to implement the functionality for a singleton in my superclass, but I would like to keep it abstract as well.

What could I do? I tried creating a reference to my interface. Silly me, that’s not allowed of course. I really don’t want to give up that the superclass is abstract, so the only way to go is to define the singleton-method get_instance in every subclass.  . But wait! The get_instance( ) method should be a static (class) method. And static methods cannot be redefined. This is a Catch-22, isn’t it?

Luckily, I am not the first one who got into this trouble. “Former member” solved this issue before me by suggesting to change the parameters of the get_instance( ) from a returning to a changing parameter in an answer to this question.  Hurrah! I can keep the superclass abstract, I can even define all subclasses as CREATE PRIVATE, although I have to define all subclasses as a friend of the superclass. So the code now looks like this:

INTERFACE: zlif_interface.
  METHODS: do_something RETURNING VALUE(zrv_text) TYPE string.
  CLASS-DATA: zgr_instance TYPE REF TO zlif_interface.
  CLASS-METHODS:
    get_instance CHANGING zcv_instance TYPE any.
ENDINTERFACE.

CLASS zlcl_super DEFINITION ABSTRACT CREATE PROTECTED.
  PUBLIC SECTION.
    INTERFACES zlif_interface.
    ALIASES:  zgr_instance FOR zlif_interface~zgr_instance,
              do_something FOR zlif_interface~do_something,
              get_instance FOR zlif_interface~get_instance.
ENDCLASS.

CLASS zlcl_super IMPLEMENTATION.
  METHOD get_instance.
    IF  zgr_instance IS INITIAL
    AND cl_abap_refdescr=>describe_by_data( zcv_instance )->kind = cl_abap_typedescr=>kind_ref.
      DATA(lo_ref_descr) = CAST cl_abap_refdescr( cl_abap_refdescr=>describe_by_data( zcv_instance ) ).
      DATA(zlv_classname) = lo_ref_descr->get_referenced_type( )->get_relative_name( ).
      DATA: zlr_instance TYPE REF TO zlif_interface.
      CREATE OBJECT zlr_instance TYPE (zlv_classname).
      zgr_instance ?= zlr_instance.
    ENDIF.
    zcv_instance ?= zgr_instance.
  ENDMETHOD.
ENDCLASS.

Nice! Now we have a singleton with inheritance. I did encounter some frustrations though. I love chaining methods, so actually I would have liked this statement:

CREATE OBJECT DATA(zlr_instance) TYPE 
  ( CAST cl_abap_refdescr( cl_abap_refdescr=>describe_by_data( zcv_instance ) 
                          )->get_referenced_type( )->get_relative_name( ) ).

That I can’t use the CAST keyword in the type-declaration I can understand. But I find it quite annoying that I can’t use a method to retrieve the type in the type-declaration. This yields a syntax error:

CREATE OBJECT DATA(zlr_instance) TYPE "This yields an annoying syntax error
  (lo_ref_descr->get_referenced_type( )->get_relative_name( ) ).

     "This one creates an annoying syntax error as well: 
CREATE OBJECT zlr_instance TYPE (lo_abap_refdescr->get_referenced_type( )->get_relative_name( ) ).

So, would it work like this? Well, asking the question is answering it. But please, take a moment to think what could go wrong here before reading further.

Redefining methods in ABAP. (And this is where it really hurts).

I ended the last paragraph with a question, what the error is in the previous solution.

The answer is that by defining the singleton-reference-variable in the interface (or the superclass), this contains the same reference for all subclasses. And if we want to use different functionalities defined in different subclasses, I would like to be able to define a singleton-reference in each subclass (that’s possible) and redefine the “get_reference” method. In JavaScript this is possible, but in ABAP it is not. Why is this? I don’t know. If you do know, please leave a comment. Why should you (not) be able to redefine a static method?

Issue #02: How to get a singleton of the correct (sub)class; if I define the get_instance in the interface (or the abstract superclass).

My solution to solve this is to keep track of the instances per subclass. Although this is a working solution, I have the feeling there might be a better solution. Again, if you know a better, nicer, cleaner solution, please tell me.
This is the (simplified) UML diagram:

UML%3A%20Factory%20for%20a%20singleton%20with%20Inheritance

UML: Factory for a singleton with Inheritance

My implementation is displayed under here. First the definition, followed by the implementation. The subclasses don’t change, so I didn’t include them.

INTERFACE: zlif_interface.
  TYPES: BEGIN OF zlty_instance,
           classname TYPE seoclsname,
           instance  TYPE REF TO zlif_interface,
         END OF zlty_instance,
         zltty_instances TYPE SORTED TABLE OF zlty_instance 
            WITH UNIQUE KEY classname
            WITH UNIQUE SORTED KEY k2 COMPONENTS instance.
  CLASS-DATA zgt_instances TYPE zltty_instances.
  CLASS-METHODS:
    get_instance CHANGING zcv_instance TYPE any.
  METHODS: do_something RETURNING VALUE(zrv_text) TYPE string.
ENDINTERFACE.


CLASS zlcl_super DEFINITION ABSTRACT CREATE PROTECTED.
  PUBLIC SECTION.
    INTERFACES  zlif_interface.
    ALIASES:    do_something  FOR zlif_interface~do_something,
                get_instance  FOR zlif_interface~get_instance.
  PRIVATE SECTION.
    ALIASES:    zgt_instances FOR zlif_interface~zgt_instances.
ENDCLASS.

Implementation of returning the singleton per class in the inheritance tree:

CLASS zlcl_super IMPLEMENTATION.
  METHOD get_instance.
"  https://answers.sap.com/questions/6203810/inherited-static-method-to-return-instance-of-sub-.html

    "Get the class-type of the requested object to be created.
    IF cl_abap_refdescr=>describe_by_data( zcv_instance )->kind = cl_abap_typedescr=>kind_ref.
      DATA(lo_ref_descr) = CAST cl_abap_refdescr( cl_abap_refdescr=>describe_by_data( zcv_instance ) ).
      DATA(zlv_classname) = lo_ref_descr->get_referenced_type( )->get_relative_name( ).

      DATA: zls_instance TYPE zlif_interface~zlty_instance.
      READ TABLE zgt_instances WITH KEY classname = zlv_classname INTO zls_instance .
      IF sy-subrc NE 0.
        DATA: zlr_instance TYPE REF TO zlif_interface.
        CREATE OBJECT zlr_instance TYPE (zlv_classname).
        zls_instance = VALUE zlif_interface~zlty_instance(
            classname = zlv_classname
            instance  = zlr_instance ).
        INSERT zls_instance INTO TABLE zgt_instances .
      ENDIF.

      zcv_instance ?= zls_instance-instance.

    ENDIF.
  ENDMETHOD.
ENDCLASS.

There it is! An implementation of a singleton with inheritance.

Nice to have

What I added in my productive solution is a method in the superclass that returns all possible classes for instances, by dynamically selecting all subclasses from the superclass that implements the interface (I’m looking at table SEOMETAREL). In this case you can both easily extend the amount of subclasses, and still implement some checks. It is also nice for a calling program, to be able to get a list of possible classes to create. In fact, I think it is so nice to have, I’ll support this in my factory!

And ofcourse, I added error-handling. The code above does not contain any error-handling, as currently it is already quite the listing.

And what would be also nice to have? A factory! Oh boy, wouldn’t that be exciting, just asking some class to give me an instance? You could even implement some logic that would let you select the subclass if the first nice-to-have was implemented. So, let’s start with a factory:

One factory class to rule them all

Factory

Factory

Since we have two nice inherited singletons, we’ll create a factory to manage easy instantiation for us. I would like two functionalities from this factory:

  1. I would like this factory to give us a list we can use (and for the sake of simplicity, I’ll give a hardcoded solution here – but it would be possible to do this with logic).
  2. And from this list I want to pick one and instantiate a singleton.

For the first part I need a table to keep track of the entries, and I need a type for that table. Furthermore, I need to fill that table. I can do that in my class-constructor. And of course I need a method to read them.

For the second part I need the factory method. My definition could look like this:

CLASS zlcl_factory DEFINITION ABSTRACT FINAL CREATE PRIVATE.
  PUBLIC SECTION.
    TYPES: BEGIN OF zlty_instances,
             instance_type TYPE string,
             classname     TYPE seoclsname,
           END OF zlty_instances,
           zltty_instances TYPE SORTED TABLE OF zlty_instances
                        WITH UNIQUE KEY  instance_type
                        WITH UNIQUE SORTED KEY k2 COMPONENTS classname.

    CLASS-METHODS:
      class_constructor,
      get_possible_instance_classes RETURNING VALUE(zrt_instances) TYPE zltty_instances,
      get_some_instance IMPORTING zip_singleton       TYPE any
                        RETURNING VALUE(zrr_instance) TYPE REF TO zlif_interface ."RAISING zlcx_error .
  PRIVATE SECTION.
    CLASS-DATA:
      zgt_instance_types TYPE zltty_instances,
      zgr_instance       TYPE REF TO zlcl_factory.
ENDCLASS.

And the implementation could look like this:

CLASS zlcl_factory IMPLEMENTATION.
  METHOD class_constructor.
    zgt_instance_types  = VALUE zltty_instances(
      ( instance_type = |one| classname = |ZLCL_SUBCLASS_ONE| )
      ( instance_type = |two| classname = |ZLCL_SUBCLASS_TWO| )
      ).
  ENDMETHOD.

  METHOD get_possible_instance_classes.
    zrt_instances = zgt_instance_types.
  ENDMETHOD.

  METHOD get_some_instance.
    DATA dref TYPE REF TO data.
    CREATE DATA dref TYPE REF TO (zip_singleton).
    ASSIGN dref->* TO FIELD-SYMBOL(<fs_ref>).

    "Create & fill the signature of the method
    DATA(ptab) = VALUE abap_parmbind_tab(
                    ( name  = 'ZCV_INSTANCE'
                      kind  = cl_abap_objectdescr=>changing
                      value = REF #( <fs_ref> ) )
                      ) .

    CALL METHOD (zip_singleton)=>('GET_INSTANCE')
      PARAMETER-TABLE ptab.

    zrr_instance ?= <fs_ref>.
  ENDMETHOD.

Now lo and behold, we have a working factory that returns us singletons in different flavours!

I am so happy, I’ll immediately start my blog about comparing different solutions for temporarily sleeping my programs.

I hope you enjoyed my blog, and learned something from it. If you did, please leave a comment!

Conclusion

In this blog I wanted to show you how to create a singleton, with inheritance, and disclosed by using a factory-class. I also showed a few of the pitfalls in creating these classes, and some quirks in ABAP. And finally, I would like an answer describing the rationale is behind a decision to allow or forbid a redefinition of a static method.

Wrap-up (and the weirdest issue)

In the complete solution with exception handling, I found another ABAP-quirk: If I define the interface before the definition of my exception-class, but after a DEFINITION DEFERRED, the editor yields a weird error. Defining the exception-class before the interface solves this. It seems that the definition deferred does not take into account any inheritance, but checking on the RAISING clause does.

Issue #03: Definition deferred yields errors for exception classes. Why, SAP, WHY?

* Weird issue: If I define my interface first, and my exception class
* second, I get this error, even if I use a DEFINITION DEFERRED for my
* exception class.
*       The class "ZLCX_ERROR" was not derived from
*       either "CX_STATIC_CHECK" or "CX_DYNAMIC_CHECK".

***  DEFINITIONS  ****************************************************
CLASS zlcx_error DEFINITION DEFERRED.

INTERFACE: zlif_interface.
  TYPES: ....
  CLASS-METHODS:
    get_instance CHANGING zcv_instance TYPE any  RAISING zlcx_error .
  METHODS: do_something RETURNING VALUE(zrv_text) TYPE string.
ENDINTERFACE.

CLASS zlcx_error DEFINITION
  INHERITING FROM cx_dynamic_check.
  PUBLIC SECTION.
    INTERFACES: if_t100_message.
...
ENDCLASS.

 

For those that would like to see a full solution including error-handling, you can find the complete, final listing here.

Assigned Tags

      11 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Jelena Perfiljeva
      Jelena Perfiljeva

      Yes, I enjoyed, and I learned, and I'm leaving the comment. 🙂

      After seeing the title in RSS feed, I opened the blog rather reluctantly expecting yet another one of those clumsy posts with obligatory lt_itab and wa_itab. 🙂 So it was a great relief right from the start.

      Really appreciate your explanation of the scenario and thought process, with good examples. Nicely done and thank you for sharing!

      Author's profile photo Jan-Willem Kaagman
      Jan-Willem Kaagman
      Blog Post Author

      Thanks for your kind words Jelena! I always like to read your posts, so I feel honoured I was able to catch your interest.

      Author's profile photo Matthew Billingham
      Matthew Billingham

      I've recently had to deal with somehow writing code that works in a 750 and a 752 system.I need to return the source code of a CDS and the source code of an associated DCL. The former works fine in 750 and 752, but the latter is somewhat more difficult in 750. In the actual implementation, I use loads of dynamic code, but that's not relevant to what I want to share.

      First I defined my interface.

      INTERFACE zif_cds_handler
        PUBLIC .
          METHODS:
          get_cds_source_code
            RETURNING
              VALUE(r_result) TYPE string
            RAISING
              zcx_cds,
          get_dcl_source_code
            RETURNING
              VALUE(r_result) TYPE string
            RAISING
              zcx_cds.
      ENDINTERFACE.

      Then I create my superclass.

      CLASS zcds_handler DEFINITION
        PUBLIC
        ABSTRACT.
        PUBLIC SECTION.
          INTERFACES zif_cds_handler.
          METHODS
            constructor
              IMPORTING
                i_cds_name TYPE csequence.
        PROTECTED SECTION.
          METHODS get_dcl_source_code ABSTRACT
            RETURNING
              VALUE(r_result) TYPE string
            RAISING
              zcx_cds.
      ENDCLASS.
      
      CLASS zcds_handler IMPLEMENTATION.
      ...
        METHOD zif_cds_handler~get_cds_source_code.
          TRY.
      ...
            CATCH cx_sy_open_sql_error cx_sy_create_data_error INTO error.
              RAISE EXCEPTION TYPE /xiting/cx_cds
                EXPORTING
                  previous = error.
          ENDTRY.
        ENDMETHOD.
      
        METHOD zif_cds_handler~get_dcl_source_code.
          r_result = get_dcl_source_code( ).
        ENDMETHOD.

      And a couple of subclasses

      CLASS zcds_handler_752 DEFINITION INHERITING FROM zcds_handler
        PUBLIC
        FINAL
        CREATE PRIVATE
        GLOBAL FRIENDS zcds_handler_factory.
      
        PUBLIC SECTION.
          METHODS
            constructor
              IMPORTING
                i_cds_name TYPE csequence.
        PROTECTED SECTION.
          METHODS
            get_dcl_source_code REDEFINITION.
        PRIVATE SECTION.
      ENDCLASS.
      
      CLASS zcds_handler_750 DEFINITION INHERITING FROM zcds_handler
        PUBLIC
        FINAL
        CREATE PRIVATE
        GLOBAL FRIENDS zcds_handler_factory.
      
        PUBLIC SECTION.
          METHODS:
            constructor
              IMPORTING
                i_cds_name TYPE csequence.
        PROTECTED SECTION.
          METHODS
            get_dcl_source_code REDEFINITION.
        PRIVATE SECTION.
      ENDCLASS.

      Finally, the factory class.

      CLASS zcds_handler_factory DEFINITION
        PUBLIC
        FINAL
        CREATE PUBLIC .
      
        PUBLIC SECTION.
          CLASS-METHODS:
            get_instance
              IMPORTING
                i_cds_name      TYPE csequence
              RETURNING
                VALUE(r_result) TYPE REF TO zif_cds_handler.
        PROTECTED SECTION.
        PRIVATE SECTION.
          CLASS-METHODS is_752_or_above
            RETURNING
              VALUE(r_result) TYPE abap_bool.
      ENDCLASS.
      
      
      
      CLASS zcds_handler_factory IMPLEMENTATION.
        METHOD get_instance.
          IF is_752_or_above( ).
            r_result = NEW zcds_handler_752( i_cds_name ).
          ELSE.
            r_result = NEW zcds_handler_750( i_cds_name ).
          ENDIF.
        ENDMETHOD.
      
        METHOD is_752_or_above.
      ...
        ENDMETHOD.
      ENDCLASS.

       

      So here I've got my shared code in the superclass, for retrieving the CDS source code, but the DCL source coderetrieval is implemented in the subclasses. The factory class is responsible for determining which instance to return. This could be made into a singleton easily enough, but for my purposes there's no need. (In fact, I've found that singletons are rarely necessary).

      This interface/factory/class technique I picked up from the Clean ABAP book. 

      Author's profile photo Jan-Willem Kaagman
      Jan-Willem Kaagman
      Blog Post Author

      Hello Matthew,

      That's a nice and elegant piece of code! It almost solves the same issue. Almost, as your code is not usable for a singleton.

      You could write:

         IF is_752_or_above( ).
            r_result = NEW zcds_handler_752=>get_instance( i_cds_name ).
          ELSE.
            r_result = NEW zcds_handler_750=>get_instance( i_cds_name ).
          ENDIF.

      This would lead to either an implementation of the get_instance method in each subclass, or a solution similar to my solution.

      So indeed, if you don't need a singleton, a lot easier solutions are possible. Thanks for sharing!

      Author's profile photo Matthew Billingham
      Matthew Billingham

      Maybe I misunderstand, but here is the singleton version.

      CLASS zcds_handler_factory DEFINITION
        PUBLIC
        FINAL
        CREATE PUBLIC .
      
        PUBLIC SECTION.
          CLASS-METHODS:
            get_instance
              IMPORTING
                i_cds_name      TYPE csequence
              RETURNING
                VALUE(r_result) TYPE REF TO zif_cds_handler.
        PROTECTED SECTION.
        PRIVATE SECTION.
          CLASS-METHODS is_752_or_above
            RETURNING
              VALUE(r_result) TYPE abap_bool.
          CLASS-DATA instance TYPE REF TO zif_cds_handler.
      ENDCLASS.
      
      
      
      CLASS zcds_handler_factory IMPLEMENTATION.
        METHOD get_instance.
          IF instance IS NOT BOUND.
            IF is_752_or_above( ).
              instance = NEW zcds_handler_752( i_cds_name ).
            ELSE.
              instance = NEW zcds_handler_750( i_cds_name ).
            ENDIF.
          ENDIF.
          r_r_result = instance.
        ENDMETHOD.
      
        METHOD is_752_or_above.
      ...
        ENDMETHOD.
      ENDCLASS.
      Author's profile photo Jan-Willem Kaagman
      Jan-Willem Kaagman
      Blog Post Author

      Nice try, Matthew, but for me this is not a pure singleton. You make a different class (the factory) responsible for the singleton class being singleton. This is a very dangerous way of programming, and hence a bad practice. If somebody else would like to use your class, that person would have no idea that it was meant as a singleton.

      Author's profile photo Matthew Billingham
      Matthew Billingham

      I don't get your point. How is it dangerous?

      You can only get instances of the subclasses via the factory, as their instantiation is PRIVATE and they're GLOBAL FRIENDS of the factory. (An the superclass is ABSTRACT of course).

       

      Author's profile photo Jan-Willem Kaagman
      Jan-Willem Kaagman
      Blog Post Author

      Perhaps dangerous is not the right word. Your solution is not encapsulated, responsibilities are shared, outside the boundary of the class-tree. Hence it is not an object oriented design, and that's why I called it dangerous. It works, that's for sure, but it makes my skin itchy 😉 . If I would like to reuse your code by making a subclass of your factory, your solution won't work. You would need to modify your singleton-class (adding the factory subclass as a friend). In my solution I can create a factory subclass without any modification to my singletons. Even more, if you would create another handler subclass, you would need to refactor your factory as well. In my complete solution (follow the github link) you could create as many subclasses as you like without changing the rest of the code.

      What I dislike in my solution is that I still need the FRIENDS keyword. I use it to make my singleton subclasses FRIENDS of the superclass. In that way, I can create instances within my inheritance tree. But generally I hate to use the FRIENDS keyword, as it is invented for lazy programmers.

      Author's profile photo Matthew Billingham
      Matthew Billingham

      What I've described is not the factory pattern as described by the Gang of Four. However, there are many ways of achieving the same (or similar) goal. From my perspective, the design patterns are not prescriptive rather they are descriptive ways of solving common problems. Not "you must do it like this", rather "you can do it like this". It doesn't mean the pattern is the only way, and the actually implementation may well vary according to programming language. As you found, inheritance and statics don't go together in ABAP, so you have to find another way. 

      What I have is a tightly coupled set of classes and one interface. So tightly coupled that the FRIENDS construct is used. I've not designed it for consumers to extend it. It's designed to be extensible by owners of the application - which means if necessary modifying the factory. (if instantiation is controlled by a db table, as BADIs are, or by naming convention, or some other technique, rather than by coded logic, there's no need to modify the factory even if a subclass is added). So long as the signature doesn't change there's no problem for consumers.

      Your design is designed, apparently, with subclassing in mind (although I don't like the use of TYPE ANY, which violates type safety and errors won't be discovered until runtime).  I don't know what problem you're addressing with your actual code, so I can't comment whether this is good design. But if you're just leaving it open to be extended, just in case, then you risk going against the "don't code what you don't need" principle.

      I rarely use subclasses  I did use them a lot when I first started doing OO but now I prefer to implement against an interface. U also use composition frequently. It made sense to use subclasses for this application. However there might be no inheritance tree at all. Or perhaps multiple super classes with subclasses.

      I don't have an issue with FRIENDS (although I used to, probably for the same reason you're uncomfortable with it). I suggest you check out the Clean ABAP book and the TDD course in OpenSAP. These demonstrating good use cases of FRIENDS.

      So long as the final solution is robust, clear, flexible, fit for purpose, etc. I really don't think it matters.

       

       

       

       

      Author's profile photo Thales Batista
      Thales Batista

      Hi, a bit of knowledge for your issue #03: that is not a bug, but a normal behavior, it has a pretty name too: Forward declaration. (ABAP) Compiler reads code line by line, at that point it only knows that zlcx_error is a class, it doesn't know (yet) that inherits an exception class.

      Author's profile photo Jan-Willem Kaagman
      Jan-Willem Kaagman
      Blog Post Author

      Thanks, this makes sense! Nice link as well, thank you.