![]() |
||
Comelio GmbH
|
Comelio-Blog > C#.NET > Adapter-Muster Entwurfsmuster (Design Patterns) in C#.NET: Adapter
Design Patterns: Adapter-Muster in C#.NETWie auch das Singleton-Muster, so ist auch das Adapter-Muster sowohl leicht erlernbar, einfach zu implementieren und oft einzusetzen. Typische Einsatzgebiete sind solche Projekte, in denen mit bereits fertigen Klassen gearbeitet werden soll, die allerdings nicht in die schöne, durch Schnittstellen oder abstrakte Klassen vorbereitet Ordnung passen wollen, sondern die für den Einsatz erst noch zurechtgestutzt werden sollen. Dabei ist es typischerweise nicht möglich, tatsächlich die Klassen zu ändern, weil sie entweder genau in der vorliegenden Weise bereits in diesem oder anderen Projekten bzw. Anwendungen genutzt werden, oder weil eine Änderung aufgrund fehlender Quelltextbearbeitungsmöglichkeit gar nicht durchgeführt werden kann. Das Adapter-Muster stellt die Lösung für eine Problemsituation dar, in der man sehr gerne eine vorhandene Klasse nutzen möchte, ihre öffentlichen Komponenten allerdings nicht zu einer vorliegenden Schnittstelle passen. Dadurch lässt sich kein polymorphes Verhalten nutzen, sondern man müsste quasi eine neue Klasse erstellen, die die benötigte Schnittstelle implementiert und kann auf die vorliegende Klasse dann doch wiederum ohne Änderungen nicht zurückzugreifen. Selbstverständlich wird die vorliegende Klasse aus unterschiedlichen Gründen gerade nicht geändert, sondern wird eine umhüllende Klasse erstellt, die exakt die geforderte Schnittstelle implementiert und daher das polymorphe Verhalten umsetzt. Innerhalb dieser umhüllenden Klasse, die als Adapter fungiert, erzeugt man nun das eigentlich benötigte Objekt der bereits vorhandenen Klasse und formt Methoden- oder Feld-/Eigenschaftsnamen, Parameterlisten und Rückgabewerte so passend um, bis man wie bei einem Adapter sowohl die Schnittstellenbedingungen erfüllt als auch der bereits vorliegenden Klasse Genüge getan hat. public interface IKoerper {
// Eigenschaften
int laenge { get; }
int breite { get; }
int hoehe { get; }
// Methoden
double errechneFlaeche();
}
Schnittstelle IKoerper
Ein Beispiel soll dieses recht einfache Vorgehen illustrieren. Aus nicht näher bekannten Gründen soll in die bereits bestehenden Formen eine weitere Form für die Abbildung eines Kreiszylinders eingefügt werden. Anstatt nun diese Klasse von Grund auf neu zu erstellen, stellt man fest, dass bereits eine von ihrer Implementierung, aber nicht von ihren öffentlichen Komponenten her nutzbare Klasse vorliegt. Diese heißt RundZylinder, speichert hoehe und breite als Felder und nicht als Eigenschaften und liefert die Fläche nicht als Methode unter dem Namen errechneFlaeche, sondern als Eigenschaft flaeche. Dennoch ist die komplizierte Formel zur Berechnung derselben genauso implementiert wie man sie für die Methode errechneFlaeche der IKoerper-Schnittstelle benutzen könnte. Diese Klasse soll benutzt werden, kann allerdings in der vorliegenden Form nicht zum Einsatz kommen, weil sie die IKoerper-Schnittstelle nicht im Geringsten implementiert. public class RundZylinder {
// Felder
public int hoehe, radius;
// Konstruktor
public RundZylinder(int hoehe, int radius) {
this.radius = radius;
this.hoehe = hoehe;
}
// Eigenschaften
public double flaeche {
get {
return 2 * Math.PI * radius * hoehe + 2 * Math.PI * Math.Pow(radius, radius);
}
}
}
Bereits fertige, aber für IKoerper unpassende Klasse
Statt nun eine neue KreisZylinder-Klasse zu erstellen, welche den Quelltext aus der anderen Klasse – sofern er überhaupt vorliegt – kopiert und von Grund auf neu implementiert, bedient man sich einer Adapter-Klasse, welche exakt die Schnittstelle IKoerper implementiert und genau ein Objekt vom Typ RundZyliner kapselt. Alle Anfragen bzgl. Eigenschaften und Methoden, welche die Schnittstelle voraussetzen und der Programmierung als Arbeitsauftrag liefern, werden nun genau auf dieses Objekt abgebildet. Dies ist im nachfolgenden Quelltext insbesondere sehr schön für die Methode errechneFlaeche zu sehen, die letztendlich einfach nur den Wert der Eigenschaft aus dem RundZylinder-Objekt, welches in dieser Klasse erstellt wird, zurückliefert. public class KreisZylinder : IKoerper {
// Felder
private RundZylinder form;
// Eigenschaften
public int laenge {
get { return 2 * form.radius; }
}
public int breite {
get { return 2 * form.radius; }
}
public int hoehe {
get { return form.hoehe; }
}
// Konstruktor
public KreisZylinder (int hoehe, int radius) {
form = new RundZylinder(hoehe, radius);
}
// Methoden
public double errechneFlaeche(){
return Math.Round(form.flaeche,2);
}
}
Umhüllende Adapter-Klasse für fertige Lösung
Die ganze Arbeit macht natürlich nur Sinn, wenn ich das dadurch nutzbare polymorphe Verhalten, welches durch die Schnittstelle aufgebaut wird, auch tatsächlich nutze. In der FormFabrik-Klasse, die hier als Klientenklasse auftritt und zufällig unterschiedliche geometrische Objekte erzeugt, lässt sich nun ganz einfach auch ein Kreiszylinder mit der bereits fertigen Implementierung aus der RundZylinder-Klasse aufrufen. Da man ausschließlich mit dem auf Basis von IKoerper erstellten Adapter arbeitet und nicht mit der originalen Klasse, kann man diesen Adapter zurückliefern, der das eigentlich unpassende RundZylinder-Objekt umhüllt und die öffentlichen Komponenten auf dieses gekapselte Objekt anwendet. public class FormFabrik {
public IKoerper erzeugeForm() {
// .. Zufallszahlenerzeugung
else if (zahl >= 2 & zahl <5){
return new KreisZylinder(3, 2);
}
// else...
}
}
Klientenklasse, die nur IKoerper verarbeiten kann
Zum Schluss folgt noch das UML-Diagramm, das für dieses Beispiel entsteht und für das Muster sehr charakteristisch ist. Innerhalb des Adapters liegt eine Komposition vor, da es die Entstehung und Zerstörung eines anderen Objektes, welches es für die Schnittstelle adaptiert / anpasst, kontrolliert. Zusätzlich wird das adaptierte Objekt automatisch zerstört, wenn das Adapter-Objekt zerstört wird. Der Adapter fügt sich passend in die Gruppe vorhandener Klassen ein, die alle jeweils eine bestimmte Schnittstelle implementieren. Daraus folgt, dass also auch eine Klientenklasse, die sich hier auf die Schnittstelle bezieht, ebenfalls mit dem adaptierten Objekt unter Verwendung eines Adapters arbeiten kann.
Adapter-Muster
Seminare
|