Das Eckige muss in das Runde … aber das passt doch gar nicht!

Hallo Spaß-Coder.

Weiter geht es mit unserer Reihe über Entwurfsmuster, heute mit einem weiteren Muster aus der Kategorie der Strukturmuster. Wir schauen uns in diesem Artikel den Adapter (engl. Adapter) an, der auch aus der Sammlung der Gang of Four stammt.

Was ist der Adapter?

Im Grunde beschreibt der Adapter mit diesem einen Wort seine Funktion schon sehr gut. Woran denkt ihr beim Wort Adapter?

Wahrscheinlich habt ihr gerade in etwa so was im Kopf:

Adapter

Das ist im Grunde genau das, was das Adapter-Muster uns bietet. Vielleicht nicht mit unterschiedlichen Stromvarianten oder Audio-/Videosignalen, aber für unseren Code.

Wenn wir eine Klasse verwenden möchten, dessen Schnittstelle nicht zu unseren Anforderungen passt und wir die vorhandene Klasse nicht ändern können oder möchten, bietet es sich an, die Schnittstelle über einen Adapter an unsere Bedürfnisse anzupassen. Darüber hinaus kann der Adapter dann sinnvoll sein, wenn die Gegenstelle zum Zeitpunkt der Implementierung noch nicht genau bekannt ist. Dann wird die Schnittstelle festgelegt und später über einen Adapter an – ggf. unpassende – Implementierung angepasst. So muss der eigentliche Produktionscode nicht angepasst werden.

 

Wie funktioniert der Adapter?

Nachdem wir den Decorator bereits kennengelernt haben, erkennen wir im Adapter die gleiche Struktur. Wir packen eine Klasse ein, aber diesmal nicht, um sie um neue Funktionen zu erweitern, sondern vielmehr, um ihre eigentliche Schnittstelle zu verbergen und durch eine neue zu ersetzen.

Nehmen wir einmal an, wir programmieren eine Finanzverwaltung, um etwa unsere Ein- und Ausgaben zu dokumentieren und auswerten zu können. Wir können dabei unter anderem die Daten von Bankkonten abrufen, also Empfänger oder Absender einer Zahlung, Soll/Haben Kennzeichen, den Betrag und den Text der Überweisung. Mehr brauchen wir nicht.

Allerdings bieten unterschiedliche Banken unterschiedliche Schnittstellen an. Schauen wir uns z.B. mal die Schnittstellen von zwei Banken an:

Die eine Schnittstelle arbeitet mit einem Array von String-Arrays und die andere mit einem String (der Doku entnehmen wir noch, dass es sich hierbei um eine JSON-Repräsentation der Daten handelt).

Wir möchten unseren Code natürlich sauber, objektorientiert strukturieren. Die Schnittstelle, welches unsere Anwendung verwendet, sieht folgendermaßen aus:

Wir sehen, das passt nicht zusammen. Aber was tun wir?

Hier kommt uns das Adapter-Muster gerade recht. Wir erstellen für jede Schnittstelle (hier die beiden oben genannten Bank-Services) einen Adapter, der …

  • unser Interface implementiert
  • den Service in Form einer Komposition verwendet
  • ggf. die Datenformate konvertiert, damit sie in die eine oder andere Schnittstelle passen

Wie sieht so ein Adapter aus? Schauen wir uns das am Beispiel einmal an:

Alle Anforderungen sind erfüllt, wir implementieren unsere Schnittstelle, d.h. unsere Anwendung kann diese Klasse verwenden. Wir nutzen den Service der Bank, indem wir ihn erstellen und bereiten zum Schluss die Daten so auf, dass sie in unsere Schnittstelle passen.

Der zweite Adapter ist nun entsprechend einfach, der Aufbau bleibt gleich:

Sicherlich ist der Code in Summe noch zu verbessern, so können wir die Konvertierung in eine eigene Klasse auslagern und die Aufrufe von new für die Services führen zu einer engen Kopplung. Für diese Problemstellungen haben wir bereits einen Menge Möglichkeiten kennengelernt (z.B. Dependency Inversion Principle).

Werfen wir zum guten Schluss noch einen Blick auf unsere Finanzverwaltungssoftware. Wie benutzen wir die Adapter denn nun?

Auch hier gibt es im Grunde keine großen Überraschungen. Wir definieren uns eine Variable für unser Interface und erstellen eine Instanz des Adapters, den wir verwenden möchten.

Für unsere Anwendung ist es dabei egal, welchen Adapter wir verwenden, die Rückgabewerte sind immer gleich aufgebaut, darum kümmert sich der Adapter. Auch logische Änderungen (z.B. könnten die Buchungen statt Soll-/Haben einfach positiv/negativ sein) kann der Adapter vereinheitlichen. So, wie wir das in unserer Anwendung brauchen.

Möchten wir nun eine weitere Bank in unsere Anwendung integrieren, brauchen wir nur einen neuen Adapter schreiben und schon kann unsere Anwendung damit arbeiten, ohne dass wir sie großartig anpassen müssen.

 

Wann ist der Adapter sinnvoll anzuwenden?

Aus dem Beispiel sollte gut hervorgehen, dass wir den Adapter dann sinnvoll einsetzen können, wenn das Interface einer Klasse nicht zum Interface des Verwenders (des Clients) passt, wir die Klasse selbst aber nicht ändern können oder dürfen. Der Adapter ist dabei eine neue Klasse, die sich zwischen die beiden zu verbindenden Klassen setzt und die Schnittstelle zum Client vereinheitlicht. Ggf. übernimmt der Adapter dabei auch Aufgaben für die Umwandlung von Datenformaten.

Der Adapter lässt sich mit beliebigen Klassen kombinieren, theoretisch könnte man sogar einen Adapter mit einem Adapter versehen. In wie weit das sinnvoll ist, steht auf einem anderen Blatt. Allerdings lässt sich der Adapter z.B. problemlos um eine Klasse legen, die wiederum mit dem Decorator-Muster um neue Funktionen erweitert wurde.

Dabei ergibt sich allerdings die gleiche Problematik, wie schon beim Dekorator. Insbesondere mit steigender Anzahl von verwendeten Adaptern ist die Suche nach einem auftretenden Fehler erschwert. Darüber hinaus sollte man sich gut überlegen, ob man einen Adapter baut. Dies ist ein Indiz (Smell) für schlechtes Design. Hat man die beiden beteiligten Klassen selbst in der Hand, sollte man über ein Refactoring nachdenken, statt mit dem Adapter ein Symptom zu bekämpfen.

 

Zusammenfassung?

Wir haben uns in diesem Artikel ein Strukturmuster aus der Liste der Entwurfsmuster angeschaut, mit dem wir die Schnittstelle zu anderen Klassen vereinheitlichen lassen, ohne diese ändern zu müssen. Damit machen wir – wie schon beim Decorator – am bestehenden Code nichts kaputt und habe so auch Einfluss auf Klassen, die eigentlich nicht in unserem Einflussbereich liegen.

Habt ihr auch schon mal ein ähnliches Problem gehabt? Wie habt ihr es gelöst? Wäre der Adapter die bessere Lösung gewesen?

Eure Spaß-Coder

Dieser Artikel basiert neben unseren Erfahrungen auf den Ausführungen aus:

  • https://de.wikipedia.org/wiki/Adapter_%28Entwurfsmuster%29
  • https://dzone.com/articles/design-patterns-uncovered-0

Code-Beispiele auf Github:

  • https://github.com/invidit/CodeQuality/tree/Adapter/DesignPattern

Bilder:

  • http://flickr.com
    • hainteractive – Roxio Easy VHS to DVD Adapter
    • Cathy Liu – LVSUN exchangeable plugs

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert