2                  Frameworks

Dieses Kapitel soll die begriffliche Grundlage legen für die spätere Beschreibung des Micrologica-Telefonie-Frameworks zum einen, und die Diskussion der Framework-Evolution zum anderen.

Der Grundgedanke von Frameworks besteht darin, Implementationen für den größten Teil einer Kategorie von Anwendungen bereitzustellen ("generische Anwendungen"). Für eine neue Anwendung werden lediglich die Teile hinzugefügt, die diese Anwendung von anderen dieser Kategorie unter­scheiden.

Ein Ziel dieses Vorgehens ist es, möglichst schnell eine Reihe ähnlicher Anwendungen mit dem Framework zu erstellen. Alle Anwendungen, die auf demselben Framework basieren, verwenden sowohl das Design als auch die Implementierung des Frameworks gemeinsam, wodurch Fehlerkorrekturen an zentraler Stelle im Framework allen abgeleiteten Anwendungen zugute kommen, so daß eine höhere Qualität der Software erreicht werden kann.

Dieses Kapitel soll einen Überblick über die Begriffswelt und Technik von Frameworks in der objektorientierten Programmierung geben.

2.1          Definition und Terminologie von Frameworks

Unter einem Framework verstehe ich in Übereinstimmung mit [BKGSZ 95] eine Konfiguration von Klassen, die den nötigen Ablauf zur Bewältigung einer Reihe verwandter Problem­stellungen beinhaltet und vergegenständlicht. Es stellt eine generische Lösung für einen Anwendungs­bereich dar, die für die jeweiligen Anwendungsfälle spezialisiert werden muß.

In [GHJV 95, S. 26f] werden Frameworks folgendermaßen definiert:

"The framework dictates the architecture of your application. It will define the overall structure, its partitioning into classes and objects, the key responsibilities thereof, how the classes and objects collaborate, and the thread of control. A framework predefines these design parameters, so that you, the application designer/implementer, can concentrate on the specifics of your application. [...] Frameworks thus emphasize design reuse over code reuse, ...".

Entscheidend ist hier bei der Begriff Design. Auf fachlicher Ebene meint Design den Vorgang der Modellbildung. Dieses fachliche Modell des Anwendungsbereichs ist der Kern des Frame­works. Auf technischer Ebene spiegelt sich die Modellbildung in den modellierten Klassen bzw. Subsystemen und deren Beziehungen untereinander wider.

Das Zitat macht deutlich, wie stark der Einfluß des Frameworks auf das Design einer speziellen Anwendung ist: Es diktiert das Design in "seinem Bereich". Alle Anwendungen, die mit dem Framework erstellt werden, müssen sich am fachlichen Modell des Frameworks orientieren und können es nur in Grenzen adaptieren. Wie wir später sehen werden, gibt es verschiedene Kategorien von Frameworks, die jeweils einen Teil einer Anwendung oder auch die gesamte Anwendung als "ihren Bereich" betrachten.

Definition 1: Software-Design

Der Prozeß der fachlichen und technischen Modellbildung für ein Software-System wird als Software-Design (im folgenden kurz Design) bezeichnet. Das fachliche Design spiegelt sich technisch in der Aufteilung des Systems in Klassen und Subsysteme und deren Beziehungen bzw. Interaktion untereinander wider.

Durch diese Vorgabe des Designs wird eine Wiederverwendung eines Designs zusammen mit seiner Implementation möglich. Gerade die Wiederverwendung auf Designebene war bisher mit Klassenbibliotheken nicht möglich und stellt den entscheidenden Vorteil bei der framework­basierten Softwareentwicklung dar.

Im Gegensatz zu Frameworks sind Klassenbibliotheken Sammlungen von allgemein verwend­baren Klassen, z.B. Container­klassen, Strings, komplexe Zahlen etc. Diese Klassen sind dazu gedacht, einzeln verwendet zu werden. Sie stellen kein Design zur Verfügung.

Definition 2: Framework (vorläufige Fassung)

Ein Framework stellt ein generisches Design zu einer Reihe verwandter Probleme zur Verfügung. Es enthält bereits einen Teil der Implementation, muß für konkrete Anwendungen aber noch spezialisiert bzw. parametrisiert werden. Die Anwendung verwendet das Framework als Ganzes, so daß auch der Kontrollfluß der Anwendung vom Framework vorgegeben wird.

Für den Begriff "Framework" gibt es auch die deutsche Übersetzung "Rahmenwerk". Ich werde jedoch durchgängig den englischen Begriff Framework verwenden, der sich inzwischen auch im Deutschen eingebürgert hat.

2.1.1      Hot Spots und Frozen Spots

Wenn ein Framework den allgemeinen Teil einer Gruppe von Anwendungen verkörpert, so stellt sich die Frage, wie man herausfindet, was denn "allgemein" und was "speziell" ist an dieser Gruppe von Anwendungen.

Unglücklicherweise ist dies eine der schwierigsten Fragen bei der Framework-Entwicklung, die sich nur mit sehr viel Wissen über den Anwendungsbereich und mit einem evolutionären Vorgehen beantworten läßt.

Wolfgang Pree (siehe [Pree 94a], [Pree 94b]) hat zwei Begriffe geprägt, die bei der Diskussion dieser Frage hilfreich sind: Die Teile des Anwendungsbereichs, die von Anwendung zu Anwendung variieren und daher im Framework flexibel gehalten werden müssen, bezeichnet er als hot spots. An den hot spots werden häufig Änderungen erwartet.

Definition 3: Hot Spot

Ein Bereich eines Frameworks, der nicht allgemein für alle Anwendungen des Frame­works definiert werden kann, wird als hot spot bezeichnet.

Im Gegensatz zu den hot spots werden die Teile des Anwendungsbereichs, die bei allen Anwendungen gleich sind und daher im Framework fest implementiert werden können, nach Pree als frozen spots bezeichnet.

Definition 4: Frozen Spot

Ein Bereich eines Frameworks, der in allen auf dem Framework basierenden Anwendungen gleich ist, wird als frozen spot bezeichnet.

Diese Begriffe sind wichtig bei der Konstruktion eines Frameworks, um auszudrücken, welche Bereiche adaptierbar sein müssen und welche festliegende Zusammenhänge implementieren. Sie liefern jedoch nur das Vokabular und nicht die Antwort. In Kapitel 3.5 werde ich beschreiben, wie die hot spots und frozen spots des Micrologica-Telefonie-Frameworks identifiziert wurden.

2.2          Techniken zur Framework-Konstruktion

Zur Konstruktion von Frameworks werden eine Reihe von Techniken der objektorientierten Konstruktion eingesetzt. Die wichtigsten dieser Techniken möchte ich im folgenden kurz vorstellen und ihre Terminologie beschreiben.

2.2.1      Vererbung und abstrakte Klassen

Da ein Framework das allgemeine Verhalten einer Gruppe von Anwendungen beschreibt, muß es für den konkreten Anwendungsfall spezialisiert werden. Die am häufigsten eingesetzte Technik hierzu ist die Vererbung.

Eine Art, Vererbung einzusetzen, ist, durch eine gemeinsame Oberklasse das Interface einer Klasse (bzw. Gruppe von Klassen) zu spezifizieren. Dadurch, daß von der Oberklasse keine Exemplare (Objekte) erzeugt werden können, wird die Funktion der abstrakten Klasse als Definition einer Schnittstelle bzw. eines Protokolls deutlich gemacht. Dies schließt nicht aus, daß in ihr bereits einige Operationen implementiert sind, die von allen Unterklassen verwendet werden.

Definition 5: Abstrakte Klasse

Eine unvollständig implementierteKlasse, von der keine Exemplare erzeugt werden können, wird als abstrakte Klasse bezeichnet.

Nach [JohnRusso 91] und [GHJV 95, S. 325ff] kann man 4 Arten von Operationen bei abstrakten Klassen unter­scheiden:

·      Abstrakte Operationen werden nicht implementiert. Dies ist Aufgabe der jeweiligen Unter­klassen. Die abstrakten Operationen spezifizieren aber das Interface aller Unterklassen. Sie fungieren als Platzhalter.

·      Hook-Operationen haben eine sehr ähnliche Funktion wie die abstrakten Operationen. Sie haben jedoch ein Default-Verhalten, so daß sie von den Unterklassen nicht zwingend über­laden werden müssen.

·      Basis-Operationen hingegen sind Operationen, die bereits in der abstrakten Klasse voll­ständig implementiert werden können.

·      Template-Operationen implementieren einen Algorithmus basierend auf anderen Operationen. Der Algorithmus wird vollständig angegeben, zur Ablauffähigkeit fehlen ihm aber die Implementationen der abstrakten Operationen.

Bevor abstrakte Klassen in einer Anwendung verwendet werden können, müssen sie spezialisiert werden. Hierzu müssen mindestens die abstrakten Operationen in der spezialisierten Klasse implementiert werden.

Abbildung 5: Spezialisierung einer abstrakten Klasse

In C++ ist eine Klasse abstrakt (auch dt. aufgeschoben, engl. deferred), wenn eine ihrer Optionen abstrakt ist. In einigen objektorientierten Sprachen (z.B. Eiffel, Java) können Klassen auch direkt als abstrakt markiert werden. Die sog. "Interfaces" in Java dürfen keine Implementationen enthalten. Auf diese Weise wird die Aufgabe der Protokoll-Definition von der Definition eines gemeinsamen Verhaltens getrennt.

In Sprachen, die keine explizite Deklaration von Interfaces unterstützen, wie z.B. C++, sollte auf andere Weise sichergestellt werden, daß von der Klasse keine Exemplare erzeugt werden können. In C++ ist dies dadurch zu erreichen, daß der Konstruktor der Klasse als 'protected' deklariert wird und somit nicht zugreifbar ist. Diese Technik ist insbesondere bei Klassen wichtig, die konzeptionell abstrakt sind, die aber keine abstrakten Operationen, sondern nur Hook-Operationen besitzen. Anderenfalls kann der Compiler das Erzeugen von Exemplaren nicht verhindern, da Hook-Operationen ja eine leere Default-Implementation besitzen.

Die Verwendung von abstrakten Oberklassen führt zu Systemen mit einer loseren Kopplung als bei direkten Beziehungen zwischen den Klassen. Kollaborierende Klassen beziehen sich so immer nur auf ein Protokoll (Interface), nicht aber auf eine konkrete Implementierung. Daher können einzelne Klassen leichter aus­getauscht werden durch solche, die das gleiche Protokoll beherrschen, aber eine andere Implementierung besitzen.

Eine typische Eigenschaft von Frameworks ist der invertierte Kontrollfluß: Bei traditioneller Anwendungsentwicklung ruft üblicherweise die Anwendung den Bibliothekscode und bestimmt somit auch den überwiegenden Teil des Kontrollflusses. Frameworks hingegen verwenden oft abstrakte Klassen und sehen abstrakte Operationen vor, in die der Anwendungscode eingefügt wird. Die Anwendung wird dann zu passender Zeit vom Framework aufgerufen. Dadurch liegt die Ablaufsteuerung nicht mehr in der Anwendung, sondern wird vom Framework definiert (vgl. [BKGSZ 95]). Oft wird auf diese Weise die gesamte Ereignisschleife gekapselt.

Die Verwendung des invertierten Kontrollflusses wird auch als Hollywood-Prinzip (nach dem Motto "don't call us, we call you") bezeichnet (siehe [Vlissides 96a]).

2.2.2      Parametrisierung

Eine Alternative zur Anpassung von Framework-Klassen durch Vererbung ist die Parametrisierung. Statt eine abstrakte Methode vorzusehen, die von der Unterklasse implementiert werden muß, wird die Übergabe eines Parameter-Objekts an die Framework-Klasse vorgesehen, an das die Ausführung der Operation delegiert wird.

Für die Parameter-Objekte muß eine Oberklasse definiert werden, die die Signatur der spezialisierbaren Operation festlegt. Diese Operation ist meist eine abstrakte Operation, es ist aber auch der Einsatz einer Hook-Operation möglich, so daß eine Spezialisierung entfallen kann.

Abbildung 6 zeigt die notwendige Klassenhierarchie bei der Spezialisierung durch Parameter-Objekte.

Abbildung 6: Anpassung einer Framework-Klasse durch Parametrisierung

Die Parametrisierung führt üblicherweise zu einer geringeren Kopplung als die Vererbung, da keine Operationen der Oberklasse aufgerufen werden müssen und die zu implementierende Schnitt­stelle meist einen geringeren Umfang hat. Andererseits ist die Spezialisierung durch Parametrisierung auf die von vornherein festgelegten Modifikationsmöglichkeiten beschränkt.

Ein weiterer wichtiger Vorteil der Parametrisierung ist die Tatsache, daß erst zur Laufzeit entschieden werden muß, welches Parameter-Objekt verwendet wird, während Vererbung zur Compilezeit stattfindet.

2.2.3      Schnittstellen-Gestaltung

Ein wichtiger Aspekt der Framework-Konstruktion besteht in der Schnittstellen-Gestaltung der Framework-Klassen. Zum einen sollte die intendierte Verwendung der Operationen für den Framework-Benutzer verdeutlicht werden. Dies kann durch eine explizite Unterscheidung in eine Kunden-Schnittstelle und einen Spezialisierungs-Schnittstelle geschehen (siehe Kapitel 2.2.3.1). Andererseits müssen auch die Auswirkungen der Schnittstellen-Gestaltung auf die Adaptierbarkeit des Frameworks berücksichtigt werden, die in Kapitel 2.2.3.2 und 2.2.3.3 diskutiert werden.

2.2.3.1     Kunden- und Spezialisierungs-Schnittstelle

Da viele Klassen in einem Framework ausdrücklich entwickelt wurden, um spezialisiert zu werden, kann man bei ihnen entsprechende Schnittstellen-Teile unterscheiden: die Kunden-Schnitt­stelle und die Spezialisierungs-Schnittstelle (vgl. [Andert 95], [Cotter 95]).

Die Kunden-Schnittstelle (engl. client API) ist der Teil der Schnittstelle, der von anderen Klassen benutzt wird, um Dienste in Anspruch zu nehmen. Sie entspricht dem öffentlichen Teil der Schnittstelle, den alle Klassen bieten, die keine speziellen Vorkehrungen zu ihrer Spezialisierung treffen.

Die Spezialisierungs-Schnittstelle (engl. subclassing API, oder inheritance interface) hingegen besteht aus den Operationen, die zur Spezialisierung der Klasse vorgesehen sind. Dies sind die abstrakten Operationen, aber auch andere Operationen, die dynamisch gebunden sind und redefiniert werden können bzw. sollen.

Diese Klassifizierung ist jedoch nicht exklusiv: In der Praxis geschieht es oft, daß eine Operation sowohl zur Kunden-Schnittstelle als auch zur Spezialisierungs-Schnittstelle gehört, d.h. eine Methode spezialisiert wird, die von Kunden der Oberklasse direkt aufgerufen wird (vgl. [Cotter 95, S. 61]).

Beim Design von Framework-Klassen ist es einerseits wichtig, diese Aufteilung dem Anwendungs­programmierer deutlich zu machen und andererseits keine zu restriktiven Einschränkungen bezüglich der Spezialisierung (subclassing) zu machen.

In C++ sollte die Unterscheidung zwischen Kunden- und Spezialisierungs-Schnittstelle u.a. durch die Zugriffsberechtigung (private / protected / public) deutlich gemacht werden (siehe Abbildung 7). Darüber hinaus gibt die dynamische bzw. statische Bindung einzelner Methoden einen eindeutigen Hinweis, wo Spezialisierungen vorgesehen sind. Da sich die beiden Schnitt­stellen jedoch überschneiden können, ist es wichtig, durch Kommentare und Doku­mentation die vorgesehene Verwendung der Methoden deutlich zu machen und sich nicht auf die Sprach­mittel von C++ zu beschränken.

 

class ocSimpleMakeCallRequest

{

    // Kunden-Schnittstelle

    public:

    void vStatusUpdate();                // statische Bindung - darf

                                         // nicht überladen werden

 

    // Spezialisierungs-Schnittstelle

    public:

    virtual void vCheckDevices();        // dynamische Bindung -

                                         // darf überladen werden

 

    // Spezialisierungs-Schnittstelle

    protected:

    virtual void vStartMakeCall() = 0;   // muß überladen werden

 

    // weder Kunden- noch Spezialisierungs-Schnittstelle

    private:

    void vInternalUpdate();

};

Abbildung 7: Kunden- und Spezialisierungs-Schnittstelle

Ein Black-Box-Framework wird daher eine sehr viel schmalere Spezialisierungs-Schnittstelle haben als ein White-Box-Framework. Eine Klasse mit schmaler Schnittstelle ist einfacher zu spezialisieren. Anderseits schränkt eine schmale Spezialisierungs-Schnittstelle die Umgangs­möglichkeiten mit unerwarteten Änderungen deutlich ein. Daher muß in jedem konkreten Anwendungs­kontext eine neue Abwägung zwischen Flexibilität und Einfachheit der Benutzung stattfinden. In [WeiGamMar 89] wird dies als das Narrow Inheritance Interface Principle bezeichnet. Wichtiger als eine schmale Spezialisierungs-Schnittstelle, die die Spezialisierungs-Möglichkeiten ausdrückt, ist es die Zahl der Operationen zu minimieren, die von einer Unterklasse überschrieben werden müssen. Eine breite Spezialisierungs-Schnittstelle, die zum größten Teil aus Hook-Operationen besteht, die nicht überladen werden müssen, ist nur wenig komplexer als eine Schnittstelle ohne die Hook-Operationen.

2.2.3.2     Trennung von Parametern und Einstellungen

Ein weiterer Gesichtspunkt bei der Gestaltung der Klassenschnittstelle ist die Kunden-Schnittstelle und hier besonders die Übergabe von Argumenten an die einzelnen Methoden: Je weniger Methoden aufgerufen werden müssen, um eine bestimmte Aufgabe zu erledigen, desto einfacher ist eine Klasse zu verwenden. Andererseits sind wenige Methodenaufrufe mit vielen Argumenten unübersichtlich und erfordern Änderungen bei allen Kunden der Klasse, falls die Argumentenlisten später verändert werden.

Um die Evolutionsfähigkeit einer Klasse zu gewährleisten, sollte unterschieden werden zwischen den Parametern, mit denen die eigentliche Verarbeitung durchgeführt wird, und den Einstellungen, die die Art und Weise der Verarbeitung beeinflussen. Während die Parameter erfahrungs­gemäß nur selten variieren, werden die Einstellungen sehr oft nach­träglich noch erweitert. Daher sollten alle Parameter zusammen einer Methode übergeben werden, nachdem vorher die Einstellungen jeweils einzeln gesetzt worden sind. Dieses Vorgehen führt zu einer übersichtlichen Übergabe von Argumenten, da für Einstellungen häufig sinnvolle Default-Werte existieren. Es muß allerdings sichergestellt werden, daß alle notwendigen Einstellungen vorgenommen wurden, bevor die Verarbeitung beginnt. Dies kann z.B. durch ein Vertrags­modell mit Zusicherungen geprüft werden.

Während beispielsweise die anzurufende Telefonnummer ein unbedingt benötigter Parameter für einen Verbindungsaufbau ist, wäre die Wartezeit auf ein Freizeichen eher eine Einstellung, die dann auch getrennt übergeben werden sollte. Die beiden Varianten sind in Abbildung 8 einander gegenüber gestellt.

 

class ocConnectionMaker_1

{

public:

    virtual void vConnect(ocString oDestination, int iTimeout);

};

 

class ocConnectionMaker_2

{

public:

    virtual void vSetTimeout(int iDuration);

    virtual void vConnect(ocString oDestination);

};

Abbildung 8: Getrennte Übergabe von Parametern und Einstellungen

Mit dieser Aufteilung läßt sich die Funktionalität einer Klasse nachträglich verändern, und nur diejenigen Kunden müssen angepaßt werden, die diese neue Funktionalität auch tatsächlich nutzen (vgl. [Meyer 90]).

Diese Technik ist nicht an die Verwendung von objektorientierten Methoden gebunden und kann auch bei imperativen Sprachen mit Gewinn eingesetzt werden (vgl. Diskussion in [Meyer 82] über FORTRAN-Bibliotheken).

2.2.3.3     Dynamische Bindung

Für die lose Kopplung von Klassen ist es oft wichtig, Objekte polymorph nur unter ihrer Ober­klasse zu verwenden. Dieses Vorgehen macht ein dynamisches Binden erforderlich, da die passende Methode der Unterklasse erst zur Laufzeit bestimmt werden kann.

Die redefinierten Methoden müssen jedoch die Semantik der Methode der Oberklasse einhalten und dürfen auch den Kontrollfluß innerhalb des Frameworks nicht stören. Daher sollte explizit dokumentiert werden, welche Methoden der Framework-Klassen von einer redefinierten Methode weiterhin aufgerufen werden müssen, um den Kontrollfluß des Frameworks nicht zu unterbrechen. Die Einhaltung dieser Bedingung muß ggf. durch Zusicherungen überprüft werden.

Eine dynamische Bindung von Methoden findet in C++ jedoch nicht automatisch statt. Der Programmierer muß dynamisch zu bindende Methoden ausdrücklich als virtual kennzeichnen. Dies wirft die Frage auf, welche Methoden einer Klasse als virtual zu deklarieren sind und welche nicht.

Eine Klasse mit virtuellen Funktionen wird durch die dynamische Bindung anpaßbar, da man Unterklassen von ihr bilden kann, die dann polymorph verwendet werden können. Bei statisch gebundenen Methoden ist dies nicht möglich, da zur Laufzeit die Methode der Oberklasse ausgeführt werden würde.

Da der Framework-Entwickler nur Vermutungen über die zukünftige Verwendung seines Frameworks haben kann, sollte er nicht unnötig Anpassungen seiner Framework-Klassen unter­binden. Damit ein Anwendungsprogrammierer die Möglichkeit hat, die Methode der Framework-Klassen zu spezialisieren, sollten diese grundsätzlich als "virtual" deklariert sein, wenn es keine zwingenden Gründe gibt, warum diese Methode auf keinen Fall überladen werden darf. Dies ist bei Frameworks insbesondere deshalb wichtig, da die Anwendungs­programmierer oft keinen Zugriff auf den Quelltext des eingesetzten Frameworks haben und daher eine solche Änderung meist nicht nachträglich durchführen können.

Da erst zur Laufzeit entschieden werden kann, welche Methode aufgerufen werden soll, ist für den Methodenaufruf ein gewisser Overhead notwendig. Anstatt direkt auf eine Methode zu zeigen, verweist der Zeiger auf eine dynamisch gebundene Methode, auf die sog. virtual table (oder kurz VTable), in der die passende Methode gesucht wird.

Der hierdurch entstehende Performance-Verlust sollte jedoch nicht überbewertet werden, da es sich um wenige Zeiger-Dereferenzierungen handelt, die nur einen kleinen Teil der insgesamt aus­geführten Operationen ausmachen. In [DriHölz 96] wird das Ergebnis diverser Simulationen unter Berücksichtigung verschiedener Prozessor-Architekturen vorgestellt. Der Performance-Verlust für die Verwendung von virtuellen Methoden im Vergleich zu direkten Funktions­aufrufen wird dort mit durchschnittlich 5-10% quantifiziert. Das Fazit der Autoren ist:

"Given that better optimizing compilers are possible, it hardly seems appropriate for programmers to compromise the structure of their programs to avoid dispatch."

Auch in [Stroustrup 94b, S. 147] wird argumentiert, daß der durch dynamische Bindung entstehende Mehraufwand zu einem guten Teil wegoptimiert werden könne, sobald fortgeschrittenere C++ Compiler vorliegen. Einige alternative Optimierungstechniken zur Verwendung in Compilern werden in [BaconSweeney 96] diskutiert.

Ein Abweichen von diesem Vorgehen sollte erst erwogen werden, wenn tatsächlich Performance-Probleme auftreten und diese durch Profiling auf das dynamische Binden zurück­geführt werden können.

Es wird deutlich, daß die Ausrichtung auf maximale Performanz beim Design der Sprache C++ in diesem Bereich zu Voreinstellungen für die Bindung von Methoden geführt hat, die einer leichten Evolution zuwiderlaufen. Eine dynamische Bindung als Voreinstellung und das explizite Anfordern von statischer Bindung wäre meiner Ansicht nach sinnvoller gewesen.

Mit der vorgeschlagenen Vorgehensweise alle Methoden dynamisch zu binden, solange kein gegenteiliger Bedarf besteht, wird auch einer speziellen Art von Speicherlecks in C++ vorgebeugt, die sonst recht schwer zu finden ist: Wenn Oberklassen von Klassen mit virtuellen Funktionen keinen virtuellen Destruktor haben, werden beim Freigeben des Speichers durch den delete-Operator nicht immer alle Destruktoren aufgerufen [Stroustrup 92, S. 230f].

Da zum Zeitpunkt der Entwicklung einer Oberklasse kaum bekannt ist, ob es einmal eine Unterklasse geben wird, die dynamisch gebundene Methoden benötigen wird, ist dieses Problem nur zu lösen, indem alle Klassen mit virtuellen Methoden auch einen virtuellen Destruktor haben. Ansonsten würde man schon aus technischen Gesichtspunkten das Spezialisieren dieser Klassen unmöglich machen. Der virtuelle Destruktor kann ohne weiteres leer sein, so daß ein guter Compiler ihn ggf. auch wegoptimieren kann.

2.3          Kategorien von Frameworks

Ein Framework stellt das Design und eine (Teil-)Implementation für ein spezielles Gebiet zur Verfügung. Anhand des modellierten Gebiets kann man Frameworks in Kategorien unterteilen. Für das jeweilige Gebiet diktieren die Frameworks das Design der Anwendung.

Definition 6: Application-Framework

Ein Framework, das zum Ziel hat, ein ablauffähiges Programm zu erzeugen, wird Application-Framework genannt. Es enthält das Design und den Kontrollfluß für eine voll­ständig lauffähige Anwendung.

Häufig anzutreffen sind die GUI-Frameworks, die das Look-and-Feel einer graphischen Ober­fläche implementieren und den gesamten Anwendungskontrollfluß (und meist auch ein leeres Fenster und Standardmenü) enthalten. Da das Erstellen von GUI-Anwendungen recht aufwendig ist, stellen die GUI-Frameworks den größten Teil der heute kommerziell verfüg­baren Frameworks dar. Oft sind GUI-Frameworks gleichzeitig auch Application-Frameworks, da sie Zugriff auf die Ereignisschleife benötigen.

ET++ ist ein bekanntes Application-Framework für C++ (vgl. [Gamma 92], [WeiGamMar 89]). Es diente als Grundlage für GUI-Anwendungen wie z.B. die C++-Entwicklungsumgebung SNiFF+ (siehe [Bischofberger 92], [Pfeiffer 97]), sowie auch als Basis für spezialisiertere Frameworks.

Definition 7: GUI-Framework

Ein Framework, das die graphische Interaktion eines Benutzers mit einer Anwendung modelliert, wird als GUI-Framework bezeichnet.

Ein Beispiel für ein GUI-Framework ist das MFC-Framework. Zur Erleichterung der Programmierung von MS-Windows hat Microsoft 1991 mit der Entwicklung eines Frame­works zur Erstellung von Windows-Anwendungen begonnen (vgl. [Wingo 95]). Das MFC-Framework wurde in C++ realisiert und ist heute Basis eines Großteils der aktuellen Windows-Anwendungen.

Application-Frameworks müssen aber keinesfalls immer GUI-Frameworks sein. Es gibt auch diverse Kategorien von Anwendungen ohne GUI, für die ein Application-Framework erstellt werden kann. Das Micrologica Telefonie-Framework (siehe Kapitel 4) ist ein Beispiel dafür. Es definiert den gesamten Kontrollfluß, der nötig ist, um die Treibersoftware für eine Telekommunikationsanlage zu erstellen.

Frameworks, die kein Design für eine ablauffähige Anwendung enthalten, können weiter unter­schieden werden in fachliche Frameworks und softwaretechnische Frameworks. Fachliche Frameworks geben ein Design für ein bestimmtes Feld aus der Anwendungswelt vor, während softwaretechnische Frameworks von dem Anwendungsgebiet unabhängige Basismechanismen bereit­stellen, die in diversen unterschiedlichen Kontexten eingesetzt werden können (z.B. die Implementierung eines Meta-Object-Protocol).

Definition 8: Fachliches Framework

Ein Framework, das einen (Teil-)Bereich der fachlichen Anwendung modelliert, bezeichnet man als fachliches Framework.

Definition 9: Softwaretechnisches Framework

Ein Framework, das eine (implementations-)technisch notwendige Rahmenarchitektur zur Verfügung stellt, bezeichnet man als softwaretechnisches Framework.

In der Praxis werden oft diverse Bereiche innerhalb eines Frameworks unterstützt. So enthalten beispielsweise fast alle fachlichen Frameworks ein gewisses Maß an softwaretechnischer Unterstützung, die auch den Anwendungen zur Verfügung gestellt wird.

Ein Beispiel für die Kombinierbarkeit von Frameworks ist das Multimedia-Framework MET++, das auf der Basis des GUI-/Application-Frameworks ET++ entstanden ist (siehe [Ackermann 96]).

Eine andere Kategorisierung von Frameworks orientiert sich an der Art, in der sie zur Erstellung konkreter Anwendung verwendet werden:

Bei einem White-Box-Framework ist die Art der Spezialisierung kaum vorgegeben. Der Anwendungsprogrammierer kann die Klasse bei Bedarf durch Vererbung spezialisieren, muß aber beim Redefinieren darauf achten, nicht den Kontrollfluß innerhalb des Frameworks zu stören. Die redefinierte Methode muß ggf. die ursprüngliche Methode der Oberklasse aufrufen, damit das Framework seine Arbeit erledigen kann. Die "gemeinsamen Invarianten" [Johnson 93], also das implizite Kommunikationsprotokoll innerhalb der Klassen, muß gewahrt bleiben. Daher ist es für die Spezialisierung der Klassen eines White-Box-Frameworks unerläßlich, sich über die interne Realisierung der Framework-Klassen bewußt zu sein. Daher der Name White-Box-Framework.

Zur der Verwendung eines Black-Box-Frameworks ist weit weniger Wissen über den internen Aufbau des Frameworks notwendig, da die Art der Spezialisierung der Framework-Klassen z.B. durch Parameter-Objekte schon vorgegeben ist. Es ist offensichtlich, daß ein Black-Box-Framework daher einfacher zu benutzen ist.

Ein Black-Box-Framework kann eine Sammlung von austauschbaren Teilimplementierungen zur Verfügung stellen, die zur Parametrisierung verwendet werden können. Im Idealfall werden Anwendungen nur durch die Komposition von existierenden Klassen erstellt. Dies ist jedoch eine idealisierte Vorstellung, da ein Framework-Entwickler nie alle Anwendungsfälle seines Frame­works vorhersehen wird, so daß der Anwender meist doch einige Klassen selbst erstellen muß.

Die zur Spezialisierung notwendigen Parameter-Objekte können entweder direkt von Framework-Klassen erzeugt werden, oder der Anwendungsprogrammierer muß zunächst eine Klasse spezialisieren, von der er dann die Parameter-Objekte erzeugt. Die Auffassungen in der Literatur gehen auseinander, ob ein Framework, das keine oder nur wenige fertige Klassen für Parameter-Objekte enthält, bereits ein Black-Box-Framework darstellt. Ich bin der Meinung, daß man es als solches bezeichnen sollte, wenn zur Erstellung der Klassen für Parameter-Objekte kein oder nur sehr wenig Wissen über den internen Aufbau des Frameworks not­wendig ist.

Man könnte sogar sagen, daß die Unterscheidung zwischen Black-Box und White-Box letztlich durch den Anwendungs-Programmierer getroffen wird. Denn er kann ein Black-Box-Framework auch an nicht zur Spezialisierung vorgesehenen Stellen in der Art eines White-Box-Frameworks spezialisieren, sofern ihm die interne Arbeitsweise des Frameworks hinreichend bekannt ist.

Definition 10: White-Box-Framework

Ein Framework, zu dessen Verwendung Wissen über den internen Aufbau notwendig ist, wird als White-Box-Framework bezeichnet.

Definition 11: Black-Box-Framework

Ein Framework, zu dessen Verwendung kein bzw. sehr wenig Wissen über den internen Aufbau notwendig ist, wird als Black-Box-Framework bezeichnet.

Mir ist es wichtig, mit den Begriffen Black-Box und White-Box zu charakterisieren, inwieweit zur Anwendung des Frameworks ein Wissen über den internen Aufbau notwendig ist - unabhängig davon, wie dies realisiert ist. Um die Realisierung der Spezialisierung von Frame­work-Klassen zu klassifizieren, erscheint mir die Begriffsbildung aus [Taligent 95] geeignet. Dort wird zwischen vererbungsbasierten (engl. inheritance based) und kompositionsbasierten (engl. composition based) Frameworks unterschieden. In den meisten Fällen sind White-Box-Frameworks vererbungsbasiert und Black-Box-Frameworks kompositionsbasiert, aber man sollte diese Kategorien nicht miteinander gleichsetzen, da die eine die Art der Verwendung und die andere die Art der technischen Realisierung beschreibt.

Definition 12: Vererbungsbasiertes Framework

Ein Framework, das zur Spezialisierung durch Vererbung gedacht ist, wird als vererbungsbasiertes Framework bezeichnet.

Definition 13: Kompositionsbasiertes Framework

Ein Framework, das zur Spezialisierung durch Parametrisierung gedacht ist, wird als kompositionsbasiertes Framework bezeichnet.

In Kapitel 2.6.3 (Abbildung 10) ist beispielhaft die Implementierung einer Dienstanforderung an eine Tk-Anlage mit vererbungsbasierter und mit kompositionsbasierter Spezialisierung gegenüber­gestellt. In Kapitel 2.6.3 diskutiere ich auch, warum Black-Box-Frameworks sehr häufig aus intensiv eingesetzten White-Box-Framework, erstellt werden.

Es ist wichtig zu betonen, daß die Begriffe White-Box- und Black-Box-Framework zwei Extreme darstellen. In der Praxis werden Frameworks oft teilweise zur White-Box- und teil­weise zur Black-Box-Verwendung geeignet sein. Auch umfassen Frameworks oft sowohl vererbungsbasierte als auch kompositionsbasierte Teile.

2.4          Die Beziehung zwischen Entwurfsmustern und Frameworks

Frameworks werden oft mit Hilfe von Entwurfsmustern (engl. design pattern) entwickelt bzw. dokumentiert, da beide Ansätze ähnliche Intentionen haben: Sie drücken gemachte Erfahrungen aus und vergegenständlichen sie. Dadurch transportieren beide Ansätze eine Design-Idee. Frameworks enthalten zusätzlich jedoch auch eine Implementation dieser Design-Idee.

Beide Ansätze unterstützen auch die Adaptierbarkeit von Designs: Entwurfsmuster werden eingesetzt, damit sich bestimmte Teile eines Designs unabhängig von anderen Bereichen entwickeln können ("Design for Change"). Dies ist eine der Grundvoraussetzungen für ein robustes Design und damit für die Wiederverwendbarkeit, da sich mit hoher Wahrscheinlichkeit die Anforderungen zumindest in Teilbereichen verschieben werden. Die Adaptierbarkeit ist auch ein wichtiges Problem bei der Framework-Entwicklung zur Realisierung der hot spots. Abbildung 9 listet einige bekannte Entwurfsmuster auf, auf die ein Framework-Entwickler zurückgreifen kann (vgl. [GHJV 95, S. 30], [MätBisch 96], [RobJohn 96]). Im Unterschied zur üblichen Darstellung in Entwurfsmuster-Katalogen sind die Muster hier anhand der veränderlichen Eigenschaft kategorisiert.

Der Einsatz von Entwurfsmustern in Frameworks hat den zusätzlichen Vorteil, daß das Design leichter kommunizierbar ist, da die Entwurfsmuster ein Vokabular zur Beschreibung der zugrunde­liegenden Design-Ideen zur Verfügung stellen. Das Framework wird dadurch leichter erlernbar.

Definition 14: Entwurfsmuster

Ein Entwurfsmuster vermittelt die Lösung eines Design-Problems in einem speziellen Kontext. Es erklärt die Design-Idee, ist aber nicht an eine konkrete Implementation gebunden.

Es ist auch denkbar, Frameworks zu erstellen, die Implementationen für diverse Entwurfs­muster zur Verfügung stellen. Hierbei ist jedoch zu bedenken, daß die z.B. in [GHJV 95] angegebenen Beispiel-Implementationen keineswegs überall eingesetzt werden können (vgl. Kapitel 5.3.2) und der Transport der Design-Idee bei Entwurfsmustern klar im Vordergrund stehen sollte.

 

Veränderliche Eigenschaft

Entwurfsmuster

Algorithmus

Strategy, Visitor

Traversions-Algorithmus

Iterator

Schritte eines Algorithmus

Template-Method

Aktionen

Command

Reaktion auf Änderungen

Beobachter

Interaktion zwischen Objekten

Mediator

Erzeugte Objekte

Fabrik-Methode, abstrakte Fabrik, Prototype, Builder

Systemkonfiguration

abstrakte Fabrik

Implementationen

Brücke

Objekt-Schnittstelle

Adapter

Objekt-Verhalten

Decorator, State

Realisierung eines Subsystems

Facade

Abbildung 9: Lose Kopplung durch Entwurfsmuster

2.5          Vorgehensweise bei der Framework-Entwicklung

Nach Definition 2 stellt ein Framework ein generisches Design für Anwendungen zur Verfügung. Dieses Design muß die Abstraktionen des Anwendungsgebietes modellieren, und die hot spots müssen hinreichend flexibel spezialisiert werden können. Um ein Framework entsprechend konstruieren zu können, ist sehr viel Anwendungserfahrung notwendig. Bei der Framework-Entwicklung ist es daher wichtig, zunächst mehrere Anwendungen zu erstellen, damit genügend Anwendungserfahrung gesammelt werden kann.

Auf der Basis der im jeweiligen Anwendungsgebiet gesammelten Erfahrung können dann die gemeinsamen Abstraktionen gesucht und analysiert werden. Aus ihnen muß sich die Struktur des zu erstellenden Frameworks ergeben.

Aus den erstellten Anwendungen können dann die als wiederverwendbar erkannten Teile in ein Framework übernommen werden. Es ist hilfreich, aber nicht zwingend notwendig, daß die Anwendungen objektorientiert erstellt wurden. Hauptsächlich kommt es darauf an, daß allgemein verwendbare Abstraktionen gefunden werden und die hot spots identifiziert werden können. Daher ist die Beteiligung von Fachleuten des Anwendungsbereiches bei der Erstellung dieser Anwendungen extrem wichtig. (Dies ist eigentlich bei jeder Anwendungsentwicklung wichtig. Fehler in der fachlichen Modellierung wiegen bei der Framework-Entwicklung jedoch ungleich schwerer.)

Um Redundanzen zu vermeiden, sollte jedes fachliche Konzept im Framework genau einmal repräsentiert sein. Bei der Konstruktion des Frameworks sollte daher der Schwerpunkt auf einem fachlich sauberen Design liegen und nicht unbedingt auf maximaler Wieder­verwendung der vorliegenden Implementationen. Es muß bedacht werden, daß das Frame­work das Design aller noch mit ihm zu erstellenden Anwendungen maßgeblich beeinflußt.

Die Framework-Entwicklung sollte nicht auf dem kritischen Pfad einer Projektentwicklung stattfinden, um einen zu großen Zeitdruck zu vermeiden. Eine tragfähige Grundstruktur ist wichtig, um spätere Änderungen am Framework soweit wie möglich zu vermeiden. Denn sobald Anwendungen auf dem Framework basieren, sind sie potentiell von Änderungen in der Framework-Struktur betroffen. Je früher mit dem Framework Anwendungen erstellt werden, desto größer ist die Gefahr, ihnen das Design eines noch nicht ausgereiften Frameworks aufzuzwingen. Daher sollte die Framework-Entwicklung nicht in Projekten stattfinden, die einem hohen externen Zeitdruck unterliegen.

Ein Beispiel für die Folgen einer Framework-Entwicklung ohne konkrete Anwendungen berichtet Scot Wingo in [Wingo 95]: Die erste (unveröffentlichte) Version der Microsoft Foundation Classes wurde "am grünen Tisch" entwickelt mit dem Ziel, eine möglichst weitgehende Abstraktion vom Windows-API zu erreichen. Nachdem die Entwickler aber massive Probleme hatten, mit dem resultierenden Framework Anwendungen zu erstellen, wurde das Framework völlig neu entwickelt und nur eine dünne Schicht an Abstraktionen über das Windows-API gelegt. Diese Version wurde als MFC 1.0 veröffentlicht, und erst später wurden schrittweise höhere Abstraktionen eingeführt, nachdem man sie zuvor in Anwendungen erprobt hatte.

2.6          Framework-Evolution

Frameworks werden erstellt, um eine ganze Reihe von Anwendungen auf ihnen aufzubauen. Diese Anwendungen benötigen eine stabile Basis. Brown und Forte folgern daher in [BrownForte 96] :

"The success of a framework depends totally on the reusability and flexibility of the objects that it [the framework] encapsulates. When building a framework, the public interface of classes within the framework must be well defined and virtually static to ensure that changes will not adversely affect systems that have been built using the framework. This means that an object designer needs to consider all the future possible uses of the object."

Ich möchte darlegen, warum dieses Vorhaben auf lange Sicht zum Scheitern verurteilt ist: Kaum ein Softwareprodukt wird einmal geradlinig entwickelt und dann nie mehr verändert. Die Veränderung im Laufe der Zeit, die Evolution, ist auch bei Frameworks eher die Regel als die Ausnahme.

Wie bereits in der Einleitung erwähnt, verwende ich den Begriff Evolution im Sinne einer "langsam, kontinuierlich fortschreitenden Entwicklung" [Brockhaus 88, S. 688]. In dieser Bedeutung möchte ich den Begriff auf die Fortentwicklung von Frameworks über ihre initiale Fertigstellung hinaus übertragen. Lange Zeit war der Evolutionsbegriff auf die biologische Evolution beschränkt. Heute wird er im übertragenen Sinn auch in vielen nicht-biologischen Disziplinen verwendet. Die Merkmale der biologischen Evolution treffen dabei häufig nur teilweise bzw. in einem metaphorischen Sinn zu (vgl. [BI 80, S. 610]). Auf die speziellere Bedeutung des Evolutionsbegriffs in der Biologie (aber auch in der Kosmologie und Philosophie) gehe ich daher nicht weiter ein.

Definition 15: Framework-Evolution

Die kontinuierliche Veränderung eines Frameworks im Laufe seines gesamten Lebenszyklus wird als Framework-Evolution bezeichnet.

Da die Framework-Evolution (wie im folgenden gezeigt wird) in sachlichen Zwängen begründet ist, kann man sich ihr als Software-Entwickler nicht widersetzen: Man muß ihr Wesen kennen und kann präventive Maßnahmen ergreifen, aber die Zukunft bleibt etwas nicht Vorhersehbares, so daß man auch immer wieder mit unerwarteten Veränderungen umgehen muß.

Ich möchte in diesem Abschnitt darlegen, warum gerade Frameworks einem ständigen Drang zur Veränderung ausgesetzt sind und warum die Auswirkungen von Veränderungen in einem Framework ungleich weitreichender sind als in anderen Software-Produkten.

Die Ausführungen sollen als Motivation dienen für die Diskussion im nächsten Kapitel über präventive Maßnahmen, um die Auswirkungen von Änderungen bereits frühzeitig in den Entwicklungs­zyklus einzubeziehen.

Zunächst möchte ich der Frage nachgehen, warum Frameworks überhaupt einer Evolution unterworfen sind.

2.6.1      Gründe für Framework-Evolution

In Kapitel 2.5 habe ich dargelegt, daß ein Framework aus Anwendungen entwickelt werden muß und nicht "am grünen Tisch" entworfen werden kann. Daher liegen ihm mehr Erfahrungen zugrunde als einer einzelnen Anwendung. Man könnte meinen, daß Frameworks dadurch eine gewisse Robustheit gegenüber Änderungen besitzen. Dieser Effekt wird jedoch kompensiert durch das Änderungspotential der Summe der auf dem Framework basierenden Anwendungen. Sobald die Anwendungen, aus denen das Framework abstrahiert wurde, auf die Benutzung des Frameworks umgestellt sind und sich dann weiter entwickeln, multipliziert sich auch das Potential für Änderungen im Framework. Diese Änderungen können dann entweder innerhalb der Anwendungen abgehandelt oder in das Framework integriert werden.

Je weniger Anwendungen man als Basis für die Framework-Entwicklung einsetzt, um die Änderungen in der frühen Entwicklungsphase zu reduzieren, desto größer wird das Potential für gravierende Änderungen im weiteren Lebenszyklus des Frameworks.

Aber auch nach der initialen Entwicklung gibt es einen starken Druck für Veränderungen in Frameworks:

Da ein Framework gemachte Erfahrung verkörpert und die gefundenen Lösungen für alle Anwendungen einer Domäne verfügbar machen soll, besteht der Drang, später gemachte Erfahrungen, z.T. erst durch den Einsatz des Frameworks machbare Erfahrungen, ebenfalls in das Framework zu integrieren. Dies ist die gleiche Motivation, die ursprünglich zur Entwicklung der ersten Framework-Version geführt hat.

Auch wenn man sich entschließt, die Funktionalität eines Frameworks nicht zu erweitern bzw. zu verändern, so müssen immer noch Fehlerkorrekturen durchgeführt haben, die ebenfalls weit­reichende Auswirkungen auf das Framework und seine Struktur haben können.

2.6.2      Gerichtete und ungerichtete Evolution

Die Evolution eines Frameworks hängt maßgeblich davon ab, ob und wie sich die Anforderungen an dieses Framework ändern. Die veränderten Anforderungen bestimmen die Richtung der Evolution. Die Anpaßbarkeit an die neuen Anforderungen bestimmt, ob das Framework mit der Entwicklung schritthalten kann.

Es sind sowohl interne als auch externe Faktoren, die zu veränderten Anforderungen führen. Eine wichtige Quelle ist z.B. der Lernprozeß aller an der Entwicklung beteiligten Personen. Dieser Lernprozeß endet nicht nach der Erstellung der ersten Beispielanwendungen, sondern er geht weiter und begleitet den gesamten Lebenszyklus des Frameworks.

Einige der zu erwartenden bzw. sich verändernden Anforderungen lassen sich bei einer gründlichen Analyse antizipieren und in das Design des Frameworks einbeziehen. Diese erwarteten Änderungen führen zu einer sog. gerichteten Evolution (engl. directed evolution) [MätBisch 96]. Ein Beispiel aus dem Telefonie-Framework ist die Erwartung, daß ständig neue Tk-Anlagen auf den Markt kommen werden, über deren Fähigkeiten jetzt noch keine Aussage möglich ist. Problematischer sind jedoch die unerwarteten Änderungen. Sie führen zu einer sog. ungerichteten Evolution (engl. undirected evolution).

Definition 16: Gerichtete Evolution

Veränderungen, die an erwarteten Stellen bzw. aus erwarteten Gründen stattfinden, werden als gerichtete Evolution bezeichnet.

Definition 17: Ungerichtete Evolution

Veränderungen, die an unerwarteten Stellen stattfinden, werden als ungerichtete Evolution bezeichnet.

Der Umfang der ungerichteten Evolution hängt ganz entscheidend davon ab, wieweit sich ein Framework für Gebiete "zuständig fühlt", für die es nicht entworfen wurde. Wenn der Bereich, den ein Framework abdeckt, nur schwammig definiert ist, kann es leicht passieren, daß neue Anforderungen entstehen, die bei der ursprünglichen Entwicklung nicht berücksichtigt wurden, während ein Framework mit einem klar umrissenen Zuständigkeits­bereich viel leichter Anforderungen als "nicht in seinem Zuständigkeitsbereich" verweigern kann.

Dies ist jedoch ein Gratwanderung: Eine extreme Einschränkung bedeutet, nur Unterstützung für eine einzige Anwendung zu liefern. Diese Unterstützung kann dann sehr spezifisch ausfallen und ist kaum Änderungsanforderungen ausgesetzt, die unerfüllbar sind. Dies ist jedoch nicht die Intention von Frameworks. Frameworks sollen immer eine Gruppe von Anwendungen unterstützen. Dies darf jedoch nicht dazu führen, daß das Framework für "alles und nichts" zuständig ist. Der Preis für eine zu weit gefaßte Zuständigkeit des Frameworks ist eine ständig variierende Basis für alle auf dem Framework basierenden Anwendungen, da das Framework ständig mit unerwarteten Änderung zu kämpfen hat und nur eine sehr generische Unterstützung für jede spezielle Anwendung geboten werden kann.

2.6.3      Typische Merkmale der Framework-Evolution

Es hat sich gezeigt, daß die meisten Frameworks einige typische Merkmale bei ihrer Evolution gemeinsam haben.

Üblicherweise ist die erste Version eines Frameworks vererbungsbasiert (vgl. [JohnFoote 88], [RobJohn 96]). Dies liegt darin begründet, daß die Framework-Klassen aus Anwendungs­klassen hervorgegangen sind, aus denen das abstrakte Konzept extrahiert wurde. Die speziellen Teile sind jetzt in Unter­klassen zu finden.

Gerade in der frühen Framework-Entwicklung sind die gefundenen Abstraktionen noch nicht so allgemein, daß man kaum Änderungen an ihnen vornehmen muß, um neue Anwendungen zu erstellen. Daher ist meistens eine Spezialisierung per Vererbung nötig, um umfassende Änderungen an der Framework-Klasse vorzunehmen. Dies führt zu White-Box-Frameworks.

Durch den mehrfachen Einsatz des Frameworks wächst der Erfahrungsschatz, um robustere Abstraktionen zur Verfügung zu stellen. Dadurch wird eine schrittweise Entwicklung des Frameworks zu einem Black-Box-Framework möglich. Sobald die typischen Modifikationen bekannt sind, die von den Anwendungen benötigt werden, können diese in den Framework-Klassen z.B. durch Parametrisierung vorgesehen werden. Abbildung 10 zeigt beispielhaft die Umformung einer Klasse von vererbungsbasierter Spezialisierung zur kompositionsbasierten Spezialisierung.

 

vererbungsbasiert

kompositionsbasiert

Abbildung 10: Vergleich der Spezialisierungs-Techniken

Zum einen erleichtern Black-Box-Frameworks die Arbeit des Anwendungsprogrammierers, und zum anderen geben sie die Möglichkeit, eine losere Kopplung zwischen Framework-Klassen und Anwendungs-Klassen zu erreichen. Die Erstellung eines Black-Box-Frameworks setzt allerdings ein viel umfassenderes Wissen über den Anwendungs­bereich und mehr Erfahrung beim Framework-Entwickler voraus, die erst im Laufe eines Lernprozesses erreicht werden kann.

Die Oberklasse von Parameter-Objekten ist meist deutlich einfacher als die von White-Box-Klassen, die spezialisiert werden müssen. Die Parameter-Klassen müssen eine viel schlankere Schnitt­stelle implementieren, so daß es dem Anwendungsprogrammierer leichter fallen wird, neue Parameter-Klassen zu programmieren als die Spezialisierung einer White-Box-Klasse zu erstellen.

Je mehr Anwender das Framework verwenden, desto schwieriger ist meist die Kommunikation der Anwender mit den Framework-Entwicklern. Wenn ein Framework die Entwickler-Organisation verläßt, verschlechtert sich die Kommunikation meist weiter. Bei entsprechend weiter Verbreitung lohnt es sich, einen größeren Aufwand innerhalb des Frameworks zu betreiben, um unsach­gemäße Benutzung zu verhindern. Diese Möglichkeit ist bei Black-Box-Frameworks leichter gegeben, da genauer bekannt ist, an welchen Stellen Anwendungs­operationen aufgerufen werden. Vor und nach diesen Operationen kann dann zur Laufzeit (in Grenzen) geprüft werden, ob diese alle ihre Pflichten erfüllt haben. Auch das Einhalten von Reihenfolge-Bedingungen etc. wird bei Black-Box-Frameworks in das Framework verlagert und dem Anwendungsprogrammierer abgenommen.

Ein Anzeichen dafür, daß adäquate Abstraktionen gefunden sind, ist das Zurückgehen der Quelltext-Größe, da mehr Code wiederverwendet werden kann. Das Zurückgehen der Quell­text-Größe ist eine typische Erscheinung eines "gereiften" Frameworks, sie sollte jedoch nicht durch maximale Code-Wiederverwendung erreicht werden. Wichtiger ist, daß die fachlichen Abstraktionen stimmig sind und möglichst wenig Redundanz der fachlichen Konzepte existiert. Dies geht dann normalerweise auch mit weniger Redundanz im Code einher.

Abbildung 11: Entwicklung der Quelltext-Größe bei ET++ [Gamma 92, S. 161]

Als Beispiel für die zurückgehende Quelltext-Größe sei hier das ET++ Framework angeführt (siehe Abbildung 11). Im Micrologica-Telefonie-Framework ist dieses Phänomen bisher nicht so deutlich zu erkennen gewesen, da es zum einen mit Anforderungen nach zusätzlicher Funktionalität (z.B. Event-Normalisierung) überlagert wurde und da zum anderen das Framework noch nicht lange genug im praktischen Einsatz ist.

Bei Verfügbarkeit einer großen Anzahl von fertigen Parameter-Objekten für ein Black-Box-Framework und einem weit­reichenden Einsatz des Frameworks kann an die Werkzeug­unterstützung beim Umgang mit dem Framework gedacht werden. In diesem Stadium wären graphische Werkzeuge zur Komposition von Anwendungen aus vorgefertigten Teilen denkbar (vgl. [RobJohn 96]). In diesem Endstadium wäre dann keine Programmierung mehr notwendig, sondern nur noch die Konfiguration fertiger Komponenten. Dies wird jedoch nur für sehr wenige, eng umgrenzte Bereiche möglich sein.

Es ist wichtig festzustellen, daß keineswegs alle Frameworks alle diese Stadien durchlaufen. Oft verbleiben sie in Zyklen auf dem Weg zwischen White-Box- und Black-Box-Framework. Aber die generelle Tendenz ist bei fast allen Frameworks zu beobachten. Damit ist auch keinerlei Wertung verbunden - bei vielen Frameworks macht es keinen Sinn, sie zu Black-Box-Frame­works auszubauen, weil sich z.B. die Anforderungen zu schnell ändern oder die zu erstellenden Anwendungen nicht homogen genug sind.

2.6.4      Auswirkungen der Framework-Evolution

Im Gegensatz zur Evolution von Anwendungen, die nur die jeweilige Anwendung betrifft, sind Frame­works explizit dazu gedacht, in einer ganzen Reihe von Anwendungen verwendet zu werden. Daher werden auch viele Anwendungen potentiell von der Evolution des Frameworks tangiert.

Zum einen hat dies den positiven Effekt, daß z.B. Fehlerkorrekturen im Framework auto­matisch in alle betroffenen Anwendungen übernommen werden, sobald eine korrigierte Version des Frameworks zur Verfügung steht. Zum anderen darf aber nicht übersehen werden, daß das Framework das Design aller auf ihm basierenden Anwendungen bestimmt und somit alle Anwendungen den Änderungen im Framework folgen müssen.

Gamma et. al. drücken es in [GHJV 95, S. 27] sehr präzise aus, wenn sie schreiben

"..., because applications are so dependent on the framework for their design, they are particularly sensitive to changes in framework interfaces. As a framework evolves, applications have to evolve with it."

Wenn nun im Framework Änderungen am Design vorgenommen werden, kann dies im ungünstigsten Fall bedeuten, daß in allen abgeleiteten Anwendungen ein massiver Änderungs­aufwand entsteht. Es kann eine Art "Domino-Effekt" entstehen, bei dem sich Veränderungen tief in die mit dem Framework erstellten Anwendungen hinein ziehen, wenn sich z.B. die im Frame­work definierten Kommunikationsprotokolle ändern.

Andererseits sind auch alle mit dem Framework erstellten Anwendungen einer Evolution unter­worfen. Dabei entstehen oft auch neue Anforderungen an das Framework. Da das Frame­work den Anforderungen einer Reihe von Anwendungen gerecht werden muß und oft zeitlich lange vor den Anwendungen erstellt wird, kann der Fall eintreten, daß das Framework den neuen Anforderungen nicht ohne massive Änderungen gerecht werden kann.

Aufgrund der Tatsache, daß sich Änderungen im Framework nicht nur in eine, sondern in viele An­wendungen auswirken, müssen Modifikationen wesentlich behutsamer durchgeführt werden als an normalen Anwendungen. In einigen Fällen wird man sich ggf. gegen Änderungen entscheiden, weil die damit verbundenen Anpassungskosten zu hoch erscheinen.

 


Last updated: 24. Aug 2005
Page maintained by Jan Willamowius
Impressum · Datenschutz
 
English: Home | Linux | Perl | Java | Eiffel | Books | Music | Jan Willamowius | Updates | Site Map
Deutsch: Home | Badminton | ISBN-Suche | Musik-Suche | Rezepte | Jan Willamowius