Einzelnen Beitrag anzeigen

choose

Registriert seit: 2. Nov 2003
Ort: Bei Kiel, SH
729 Beiträge
 
Delphi 2006 Architect
 
#7

Re: Java IO Applikationen - Brauche Erklärungen :)

  Alt 11. Nov 2003, 20:42
Das Dekorierer-Muster (Decorator) stammt nicht von mir. Ich habe es in dem DesignPattern-Buch der Gang of Four (GoF) kennengelernt und versuche es hier in meinen Worten in Anlehnung an Dein Bsp zu beschreiben:

Häufig sollen Klassen um gewisse Funktionen erweitert werden. Recht häufig wird zu diesem Zweck die Vererbung von Klassen eingesetzt: Methoden werden überschrieben, in ihrer Sichtbarkeit verändert oder neue Methoden und Eigenschaften hinzugefügt.
Im Bsp der Streams gibt es nun für die Eingabe zB einen FileInputStream, mit dessen Hilfe Dateien eingelesen werden können.
Liest man aus ihm Byte für Byte, sollte eine Art Puffer implementiert werden, damit die Performance, gedrosselt durch den langsamen Zugriff auf den tertiären Datenspeicher, erträglich bleibt. Der unbedarfte OOP-Entwickler, könnte nun auf die Idee kommen, eine neue Funktionalität für die Pufferung zu implementieren, die er vielleicht über eine neue Eigenschaft (BufSize) zugänglich macht.

Nach einiger Zeit stellt man fest, das man von Zeit zu Zeit mit komprimierten Dateien arbeitet. Der Stream konnte erweitert werden, allerdings nur unter Zuhilfenahme einer Bibliothek von dritten, die das Programm vergrößert und in vielen Fällen nicht erwünscht ist, also: Neue Klasse, sagt der unbedarfte OOP-Entwickler. Man könnte nun die Klasse FileInputStream ableiten und die neue Funktionalität in den Erben FileInputDecompressStream implementieren, der den bisherigen Puffer und das Lesen aus Dateien erbt. Je nachdem, ob das Dekomprimieren gewünscht ist, wird die eine oder andere Klasse benutzt (siehe: Farbrik-Muster).
Die plötzliche Forderung, das auch mit Verschlüsselungen gearbeitet werden soll, ist weiter kein Problem, zwar binden wir hierzu erneut eine Bibliothek ein, was ebenfalls mitunter nicht gewollt ist, aber durch das Anlegen der weiteren Erben FileInputDecryptStream (entschlüssendes gepuffertes Lesen aus Dateien) und FileInputDecompressDecryptStream (entschlüsselndes gepuffertes Lesen aus Dateien, die komprimiert sind) bekommen wir auch das in den Griff.

Hier erkennt man vielleicht schon das Dilemma... Aber was passiert erst, wenn der Stream morgen zunächst Entschlüsselt und anschließend entpackt werden soll? Was, wenn die Quelle vom Dateizugriff in einen Netzwerkzugriff geändert werden soll? Was ist, wenn wir eine Protokollfunktion implementieren wollen? Und wie bekommen wir alle Bugs aus den immer komplizierter werdenden Klassen heraus?
Wollte man jede denkbare Kombination implementieren, würde die Anzahl der Klassen explodieren!

Eine gängige Lösung ist hier das Dekorierer-Muster (Quelle: GoF), das die Absicht hat, eine flexible Alternative zur Unterklassenbildung anzubieten, um Klassen zu erweitern.
Das Konzept stützt sich auf die in der OOP gängigen Ideen des Geheimnisprinzips und der Zuständigkeit:
Es gibt eine abstrakte Oberklasse, den InputStream, die lediglich abstrakte Methoden zum Lesen anbietet. Ein konkreter Erbe, zB der FileInputStream implementiert diese Methoden so, dass aus Dateien, ein anderer, zB der NetworkInputStream, realisiert sie, indem aus einer Netzwerkverbindung gelesen wird.
Die vorhin angesprochene Pufferung der Daten, ist für beide Streams interessant und ist im Wesentlichen nicht deren Aufgabe. Stattdessen implementieren wir einen BufferedInputStream, der weder aus einer Datei noch von der Tastatur oder dem Netztwerk liest, sondern von einem anderen, beliebigen InputStream (also zB ein FileInputStream, aber auch ein anderer BufferedInputStream selbst), auf den er eine Referenz inne hat. Er "umgibt" (wrapping) ihn sozusagen und "schmückt" oder "dekoriert" (decorates) ihn durch neue Funktionalität (hier: die Pufferung).

Und das ist im Wesentlichen schon das ganze Muster: Das äußere, ausschmückende Objekte heißt "Dekorierer" und hat dieselbe Schnittstelle wie das innere Objekt (weil sie eine gemeinsame Oberklasse haben und sich der Dekorierer ausschließlich auf die dort formulierte Schnittstelle beruft). Der Klient (der Programmierer) kann ihn also so benutzen, wie das dekorierte Objekt selbst (Transparenz).
Ein weiterer Dekorierer (DecompressInputStream) könnte nun einen Stream so dekorieren, dass die enthaltenden Daten entpackt werden. Dabei ist es egal, als welchem konkreten InputStream die Daten tatsächlich bezogen werden...

So ist eine beliebige Schachtelung möglich, während die Handhabung für den Entwickler vollständig transparent bleibt (eine Methode erwartet ein Exemplar von InputStream, der konkrete Typ und die Tatsache, er ggf andere Streams dekoriert, ist irrelevant), die Zuständigkeiten geklärt sind (jede Klasse hat genau eine Aufgabe), und ohne, dass es zu eine Explosion von neuen Unterklassen kommt oder man bei der Entwicklung von mit immer komplexeren "Gott-Klassen" die Nerven verliert.

Puh! Geschafft
Ich hoffe, dass ich Dir mit dieser Erklärung helfen konnte.
gruß, choose
  Mit Zitat antworten Zitat