Adapter-Muster richtig einsetzen - Schnittstellen sauber verbinden

8. Juni 2026

USB-C-Adapter mit und ohne Pull-Down-Widerstände. Ein Anschluss für das Netzteil, der andere für Endgeräte.

Inhaltsverzeichnis

Eine neue Bibliothek passt fachlich perfekt, aber ihre Schnittstelle spricht eine andere Sprache als der bestehende Code. Genau an dieser Stelle hilft das Adapter-Muster, im Englischen als adapter pattern bezeichnet. Ich zeige, wie es inkompatible Schnittstellen verbindet, welche Rollen beteiligt sind, wann die Lösung sinnvoll ist und welche Fehler in der Praxis häufig auftreten.

Das Adapter-Muster verbindet getrennte Softwareschnittstellen sauber

  • Zweck: Eine vorhandene Klasse oder API wird für eine benötigte Schnittstelle nutzbar.
  • Prinzip: Der Adapter übersetzt Methoden, Datenformate und Fehler zwischen zwei Komponenten.
  • Typischer Einsatz: Legacy-Code, externe Dienste, Zahlungsanbieter und wechselnde Bibliotheken.
  • Bevorzugte Variante: Objektadapter mit Komposition ist meist flexibler als Vererbung.
  • Wichtige Grenze: Ein Adapter löst Schnittstellenkonflikte, aber keine grundlegend falsche Fachlogik.

Diagramm zeigt den **adapter pattern**: Client nutzt Adapter, um mit Adaptee zu kommunizieren, der eine spezifische Operation bereitstellt.

Warum das Adapter-Muster Schnittstellen rettet

In gewachsenen Anwendungen treffen ständig unterschiedliche technische Welten aufeinander. Eine interne Anwendung erwartet etwa eine Methode namens createInvoice(), während ein externer Dienst nur submitBillingDocument() anbietet. Beide Systeme können dasselbe fachliche Ziel verfolgen, sind aber ohne Übersetzung nicht direkt kompatibel.

Der Adapter sitzt genau zwischen diesen Komponenten. Er stellt dem aufrufenden Code die erwartete Schnittstelle bereit und übersetzt die Aufrufe intern in das Format, das die fremde oder ältere Komponente versteht. Der bestehende Code muss dadurch nicht an jede neue Bibliothek angepasst werden.

Ich sehe das Muster besonders häufig an Systemgrenzen. Dazu gehören REST-APIs, Datenbanktreiber, Cloud-Dienste, Zahlungsanbieter, Logging-Bibliotheken und ältere Module, deren Quellcode nicht mehr verändert werden soll oder kann.

Der große Vorteil liegt in der Entkopplung. Die Geschäftslogik kennt nur eine stabile Abstraktion, während technische Änderungen im Adapter bleiben. Wechselt beispielsweise der Zahlungsanbieter, lässt sich oft ein neuer Adapter entwickeln, ohne die Bestelllogik umzuschreiben.

So arbeiten Ziel, Adapter und Fremdklasse zusammen

Das Muster besteht aus vier zentralen Rollen. Die Zielschnittstelle definiert, was der Client verwenden möchte. Der Client enthält die eigentliche Anwendungs- oder Geschäftslogik und arbeitet ausschließlich mit dieser Schnittstelle.

Die Fremdklasse, häufig auch Adaptee genannt, besitzt die benötigte Funktion bereits, verwendet aber andere Methodennamen, Parameter oder Datenstrukturen. Der Adapter verbindet beide Seiten, indem er die Zielschnittstelle implementiert und die Fremdklasse entweder hält oder erweitert.

Rolle Aufgabe Beispiel
Client Nutzt die gewünschte Abstraktion Bestellservice
Target Definiert den erwarteten Vertrag PaymentGateway
Adapter Übersetzt Aufrufe und Daten StripePaymentAdapter
Adaptee Stellt die inkompatible Funktion bereit SDK eines Zahlungsanbieters

Objektadapter mit Komposition

Bei einem Objektadapter wird die Fremdklasse als Objekt eingebunden. Diese Form nutzt Komposition statt Vererbung und funktioniert deshalb in praktisch allen objektorientierten Sprachen. Außerdem kann die eingebundene Abhängigkeit leichter ausgetauscht oder in Tests durch ein Mock-Objekt ersetzt werden.

interface PaymentGateway {
    PaymentResult pay(Money amount);
}

class ExternalPaymentSdk {
    ExternalResult charge(int cents, String currencyCode) {
        // Aufruf des externen Dienstes
    }
}

class PaymentAdapter implements PaymentGateway {
    private final ExternalPaymentSdk sdk;

    PaymentAdapter(ExternalPaymentSdk sdk) {
        this.sdk = sdk;
    }

    @Override
    public PaymentResult pay(Money amount) {
        ExternalResult result =
            sdk.charge(amount.cents(), amount.currency());

        return new PaymentResult(
            result.isApproved(),
            result.transactionId()
        );
    }
}

Der Client sieht nur PaymentGateway. Er muss weder die Methode charge() kennen noch wissen, dass der externe Dienst Beträge in Cent erwartet. Genau diese kleine Übersetzungsschicht hält die Fachlogik unabhängig von technischen Details.

Klassenadapter mit Vererbung

Ein Klassenadapter erbt von der Fremdklasse und implementiert gleichzeitig die Zielabstraktion. Das kann in Sprachen mit Mehrfachvererbung funktionieren, ist aber deutlich unflexibler. In Java, C# oder TypeScript ist die Kompositionsvariante deshalb meist die bessere Wahl.

Vererbung bindet den Adapter stärker an die konkrete Fremdklasse. Das wird problematisch, wenn die Bibliothek später ausgetauscht wird, wenn mehrere Instanzen benötigt werden oder wenn die Fremdklasse nicht erweitert werden darf. Ich würde diese Variante nur einsetzen, wenn die Sprache und die konkrete Klassenhierarchie sie wirklich nahelegen.

Ein praktisches Beispiel mit einer externen Zahlungs-API

Angenommen, eine Webanwendung besitzt bereits eine interne Zahlungsabstraktion. Der Bestellprozess erwartet eine einfache Methode, die einen Betrag entgegennimmt und ein internes Ergebnis zurückgibt. Das SDK eines Anbieters arbeitet dagegen mit eigenen Request-Objekten, Cent-Beträgen und speziellen Ausnahme typen.

type PaymentResult = {
  approved: boolean;
  transactionId?: string;
};

interface PaymentGateway {
  pay(amountInEuro: number): Promise;
}

class ProviderSdk {
  async createCharge(request: {
    amount: number;
    currency: string;
  }) {
    return {
      status: "succeeded",
      id: "tx_123"
    };
  }
}

class ProviderAdapter implements PaymentGateway {
  constructor(private sdk: ProviderSdk) {}

  async pay(amountInEuro: number): Promise {
    const response = await this.sdk.createCharge({
      amount: Math.round(amountInEuro * 100),
      currency: "EUR"
    });

    return {
      approved: response.status === "succeeded",
      transactionId: response.id
    };
  }
}

Der Adapter erledigt hier mehr als eine reine Umbenennung. Er übernimmt die Einheitenumrechnung, setzt die Währung, übersetzt das Ergebnis und kapselt die Eigenheiten des Anbieter-SDKs. Dadurch bleibt im Bestellservice eine verständliche Fachsprache erhalten.

In einem produktiven System sollte der Adapter zusätzlich Fehler übersetzen. Eine herstellerspezifische Ausnahme wie ProviderTimeoutException gehört meist nicht direkt in die Geschäftslogik. Sinnvoller ist eine interne Ausnahme wie PaymentUnavailableException, die unabhängig vom Anbieter verstanden wird.

Auch Datenmapping gehört hierher. Wenn der externe Dienst Kundennamen in getrennten Feldern erwartet, das interne Modell aber einen vollständigen Namen verwendet, ist der Adapter der richtige Ort für diese Umwandlung. Er sollte jedoch keine komplexen Geschäftsentscheidungen treffen, etwa ob eine Bestellung freigegeben werden darf.

Wann sich die Lösung lohnt und wann nicht

Ein Adapter ist besonders sinnvoll, wenn eine Abhängigkeit bereits existiert, aber nicht zur eigenen Architektur passt. Das gilt auch dann, wenn die Fremdkomponente technisch gut funktioniert und eine Änderung an ihr unnötig riskant wäre.

  • Legacy-Code: Alte Klassen werden schrittweise in eine modernere Architektur eingebunden.
  • Externe APIs: Ein Anbieter wird hinter einer eigenen Schnittstelle verborgen.
  • Mehrere Implementierungen: Verschiedene Dienste bieten dieselbe fachliche Funktion an.
  • Testbarkeit: Eine schwer testbare Bibliothek wird durch eine kontrollierte Abstraktion ersetzt.
  • Datenmigration: Alte und neue Datenmodelle können vorübergehend zusammenarbeiten.

Überflüssig wird das Muster, wenn beide Klassen ohnehin dieselbe Schnittstelle besitzen. Dann genügt eine direkte Abhängigkeit oder eine normale Implementierung. Einen Adapter nur aus Prinzip einzuführen, erzeugt zusätzliche Dateien und macht den Code schwerer nachvollziehbar.

Auch bei stark unterschiedlichen Fachmodellen reicht eine dünne Übersetzung manchmal nicht aus. Wenn neben technischen Feldern auch Regeln, Berechtigungen, Preislogik und Zustandsänderungen vermischt werden müssen, braucht es eher einen eigenen Anwendungservice oder eine Anti-Corruption Layer aus dem Domain-Driven Design.

Meine Faustregel lautet deshalb: Der Adapter darf technische Unterschiede erklären, aber er sollte nicht zum geheimen Ort für die gesamte Geschäftslogik werden.

Adapter, Fassade und Dekorator im direkten Vergleich

Diese Muster werden oft verwechselt, weil alle eine zusätzliche Klasse zwischen den Client und andere Komponenten setzen. Ihr Zweck ist jedoch verschieden. Die entscheidende Frage lautet, ob eine Schnittstelle übersetzt, vereinfacht oder erweitert werden soll.

Muster Hauptziel Typische Frage
Adapter Inkompatible Schnittstellen verbinden Wie kann ich diese Komponente unter meinem Vertrag nutzen?
Fassade Komplexes Subsystem vereinfachen Wie biete ich einen einfacheren Einstieg an?
Dekorator Verhalten dynamisch erweitern Wie ergänze ich Logging, Cache oder Messung?
Bridge Abstraktion und Implementierung entkoppeln Wie entwickle ich beide Hierarchien unabhängig weiter?

Ein Adapter verändert also vor allem die Form der Zusammenarbeit. Eine Fassade kann dagegen dieselben Schnittstellen verwenden und lediglich mehrere Aufrufe hinter einer einfacheren Methode bündeln. Ein Dekorator sollte die Schnittstelle normalerweise beibehalten und zusätzliches Verhalten hinzufügen.

Typische Fehler und ein sicherer Praxistest

Zu viel Logik im Adapter

Wenn ein Adapter plötzlich Preise berechnet, Benutzerrechte prüft und Bestellungen endgültig freigibt, ist seine Verantwortung zu groß. Er sollte sich auf Übersetzung, Mapping und technische Fehlerbehandlung konzentrieren. Fachliche Regeln gehören in die Domäne oder in einen passenden Service.

Die Fremdklasse wird überall sichtbar

Ein Adapter verliert seinen Nutzen, wenn im restlichen Projekt weiterhin direkte Aufrufe an das Anbieter-SDK stehen. Die Abhängigkeit sollte möglichst an einer klaren Stelle eingeführt werden. Dependency Injection hilft dabei, den Adapter im Konstruktor zu übergeben und die interne Geschäftslogik sauber zu halten.

Fehler werden unkontrolliert weitergereicht

Externe APIs liefern oft eigene Statuscodes, Ausnahme typen und Fehlermeldungen. Diese Details sollten nicht ungefiltert nach außen gelangen. Ich empfehle eine kleine, dokumentierte Übersetzungsschicht mit internen Fehlerklassen und klarer Entscheidung, welche Fehler wiederholt werden dürfen und welche nicht.

Lesen Sie auch: Jenkins erklärt - Pipelines, Plugins und der richtige Einsatz

Der Vertrag wird nicht getestet

Ein Adaptertest sollte nicht nur prüfen, ob eine Methode aufgerufen wurde. Entscheidend ist, ob Beträge, Datumswerte, Zeitzonen, Statuswerte und Fehler korrekt übertragen werden. Bei externen Diensten ergänze ich meist drei Testarten:

  1. Unit-Tests prüfen Mapping und Übersetzungslogik mit Testdoubles.
  2. Contract-Tests prüfen, ob der Adapter den erwarteten Anbieter-Vertrag einhält.
  3. Integrationstests prüfen ausgewählte reale Abläufe gegen eine Sandbox oder Testumgebung.

Gerade bei Geldbeträgen, Datumsformaten und Zeichencodierung lohnt sich diese Sorgfalt. Ein Adapter kann syntaktisch korrekt arbeiten und trotzdem fachlich falsche Werte übertragen. Solche Fehler sind in der Produktion deutlich teurer als ein paar zusätzliche Tests.

Eine gute Entscheidung beginnt an der Systemgrenze

Das Adapter-Muster ist keine komplizierte Technik, sondern eine bewusste Grenze zwischen zwei unterschiedlichen Verträgen. Es schützt den eigenen Code vor fremden Methodennamen, Datenformaten und technischen Sonderfällen.

Ich würde zuerst eine kleine, fachlich verständliche Zielabstraktion definieren und danach den Adapter darum bauen. Bleibt diese Schnittstelle stabil, kann dahinter ein SDK, ein Legacy-Modul oder ein anderer Dienst ausgetauscht werden, ohne dass die zentrale Logik mitwandern muss.

Die beste Umsetzung ist meist unspektakulär: wenige Methoden, klares Mapping, übersetzte Fehler und gute Tests. Genau dadurch wird aus einer zusätzlichen Klasse ein echter architektonischer Vorteil statt nur eine weitere Verpackung im Projekt.

Häufig gestellte Fragen

Ein Objektadapter nutzt Komposition und bindet die Fremdklasse als Objekt ein. Dadurch lässt sich die Abhängigkeit leichter austauschen oder in Tests durch ein Mock-Objekt ersetzen. In Java, C# und TypeScript ist diese Variante meist flexibler als Vererbung.

Er übersetzt Methodennamen, Datenstrukturen und Einheiten. Im Beispiel wandelt der Adapter Eurobeträge in Cent um, setzt die Währung auf EUR und überführt das externe Ergebnis in ein internes PaymentResult. Auch anbieterspezifische Fehler können in interne Fehlerklassen übersetzt werden.

Es eignet sich besonders für Legacy-Code, externe APIs, mehrere Implementierungen eines Dienstes, schwer testbare Bibliotheken und die zeitweise Zusammenarbeit alter und neuer Datenmodelle. Wenn beide Klassen bereits dieselbe Schnittstelle besitzen, ist ein Adapter meist überflüssig.

Ein Adapter sollte technische Übersetzung, Mapping und Fehlerbehandlung übernehmen, aber keine komplexen Geschäftsentscheidungen wie Preislogik oder Freigaben. Unit-Tests prüfen das Mapping mit Testdoubles, Contract-Tests den Anbieter-Vertrag und Integrationstests ausgewählte Abläufe in einer Sandbox oder Testumgebung.

Artikel bewerten

Bewertung: 0.00 Stimmenanzahl: 0

Tags:

apis adapter-muster objektadapter legacy-code datenmapping

Beitrag teilen

Jose Hempel

Jose Hempel

Mein Name ist Jose Hempel und ich beschäftige mich seit 3 Jahren intensiv mit Webentwicklung, digitaler Strategie und künstlicher Intelligenz. Diese Themen faszinieren mich, weil sie die Art und Weise, wie wir arbeiten und interagieren, grundlegend verändern. Auf metawebart.de teile ich mein Wissen und meine Erkenntnisse, um komplexe Sachverhalte verständlich zu machen und Ihnen zu helfen, die Potenziale dieser Technologien für sich zu nutzen. Dabei lege ich Wert darauf, Informationen gründlich zu recherchieren, verschiedene Perspektiven zu beleuchten und stets aktuelle Entwicklungen im Blick zu behalten, damit Sie stets nützliche und verlässliche Einblicke erhalten.

Kommentar schreiben