Donnerstag, 7. Juni 2007

Lokale Klassen

Lokale Klassen (das ABAP-Analogon zu inneren Klassen) kommen durch ABAP Unit wieder neu zu Ehren. Tests werden in lokalen Klassen des zu testenden Programms hinterlegt. Diese lokalen Klassen sind durch den Zusatz for testing als Testklassen ausgezeichnet. Die Unit Test Laufzeit erfasst alle mit dem Zusatz for testing gekennzeichneten Methoden und führt sie aus.

Während lokale Klassen für das Testen ideal geeignet sind (denn ein Test ist spezifisch für ein bestimmtes Programm), sollten sie für den Entwurf des produktiven Codes normalerweise vermieden werden. Es ist besser, sich eine passende Schnittstelle zu überlegen und eine globale Klasse mit dem Class Builder zu erstellen. Das erfordert natürlich die Arbeit am Entwurf: Was soll mein Objekt können, wie redet es mit anderen Objekten, was ist öffentlich, was privat. Wenn man mit lokalen Klassen arbeitet, kann man sich um all diese Fragen drücken. Denn "öffentlich" bedeutet für lokale Klassen im allgemeinen nur Sichtbarkeit im einbettenden Programm.

Vor allem drückt man sich mit lokalen Klassen um die Frage: Welche Teile meiner Software sind von anderen wiederverwendbar. Man hat keine Abstraktionslayer, kein "Oben-Unten", um allgemeinen Code von spezifischem Code zu unterscheiden, sondern man erklärt einfach alles als spezifisch. So bekommt man keine lästigen Anfragen oder Supportfälle von Kollegen, die an den eigenen Funktionen interessiert sind und sie in ihrem Code verwenden wollen. Im Extrem sieht man das in Funktionsgruppen wie der MEGUI. Das ist eine Funktionsgruppe mit nicht weniger als 88 lokalen Klassen im Bauch.

Man könnte einwenden: "Was geht es dich an, wenn ich meine Funktionsgruppe intern mit lokalen Klassen strukturiere? Kümmere dich um deine eigenen Angelegenheiten!" Zugegeben, Teilfunktionalität in lokale Klassen zu verlagern ist immer noch besser als mit Unterprogrammen zu arbeiten. Aber kann es sein, dass von dem gesamten Code von 88 lokalen Klassen keine einzige Zeile wiederverwendbar ist? Egal, wie man diese Frage beantwortet: Auf jeden Fall stimmt etwas mit dem Software-Design nicht. Wenn es wiederverwendbare Teile gibt, sollten diese von anderen Programmen aufgerufen werden können. Wenn es aber in 88 Klassen keine einzige wiederverwendbare Programmzeile gibt, muss leider der ganze Entwurf als misslungen betrachtet werden.

Man kann übrigens durchaus einen Abstraktionslayer für lokale Klassen einführen, indem man sie von globalen Klassen erben lässt:


class lcl_special definition
inheriting from zcl_general.
...


Dabei steht zcl_general für die im Class Builder erstellte Klasse. Das kann beispielsweise eine abstrakte Klasse sein. Wiederverwendbaren Code verschiebt man in zcl_general, programmspezifischen Code belässt man in der lokalen Klasse.

Wenn das einbettende Programm, die einbettende Funktionsgruppe oder einbettende globale Klasse eine get_instance Funktion anbietet, kann sogar die lokale Instanz in fremden Kontexten verwendet werden. Hier das Beispiel eines Unterprogramms, das extern gerufen werden kann, um die Instanz zu besorgen.


...
data: go_special type ref to lcl_special.
...
form get_special changing eo_special type ref to zcl_general.
if go_special is not bound.
create object go_special.
endif.
eo_special ?= go_special.
endform.


Aber wenn man derartige Konstruktionen macht, stellt sich die Frage: Warum dann nicht gleich die ganze lokale Klasse in den Class Builder schieben und als Kind von zcl_general deklarieren?

Dennoch will ich lokale Klassen nicht völlig ablehnen. In manchen Fällen ist ihr Gebrauch geradezu ideal - nämlich immer dann, wenn man spezifischen Code hat und diesen innerhalb des bestehenden Objektes noch besser kapseln möchte. Ich sehe folgende Einsatzgebiete für lokale Klassen:

  • Testklassen sind lokal, denn sie sind Reflexionen oder Kontrollinstanzen des aktuellen Programms.
  • Ereignisbehandler sind oft nötig, um eine Kopplung, eine Transmission zwischen auslösendem und behandelndem Programmteil zu ermöglichen. Wenn das behandelnde Programmteil keine Methode ist (dann könnte man sie gleich in der Workbench als Behandler deklarieren), sondern etwa ein Funktionsbaustein oder ein Unterprogramm, so bedarf es einer Hilfsklasse, die das Ereignis dispatcht. Diese Hilfsklasse hat eine so spezifische Aufgabe, dass sich eine Verallgemeinerung nicht lohnt, man wird sie in der Regel als lokale Klasse implementieren.
  • Automatisch generierte Klassen. Wie eine globale Klasse automatisch zu generieren ist, entzieht sich meiner Kenntnis. Vermutlich ist es kompliziert. Die "automatische Generierung des kleinen Mannes" ist dagegen immer noch die ABAP-Anweisung insert report lv_repid from table lt_code., wobei lt_code die Codezeilen enthält. lt_code kann einen Subroutinenpool mit einer lokalen Klasse und einem Unterprogramm zur Instanzbeschaffung enthalten, das wie oben dargestellt gestaltet ist. Externe Aufrufer beziehen sich auf einen im Class Builder definierten Objekttyp, auf eine Klasse oder ein Interface. Das vom Unterprogramm zurückgegebene Objekt ist dann die jeweilige Spezialisierung. Ich habe dieses Konzept in meinem "BSP-Praxisbuch" im Kapitel 18 am Beispiel des "Generischen Tabellenhandlers" beschrieben.
  • Auch in Beispielprogrammen, in denen man z.B. einen Vererbungsbaum simuliert und gewisse OO-Features ausprobieren möchte, sind lokale Klassen ideal.

In allen anderen Fällen ist es aus den oben angeführten Gründen meist besser, gleich mit dem Class Builder eine globale Klasse anzulegen.

Keine Kommentare :