Ein Adapter hilft zwei zunächst inkompatible Schnittstellen miteinander zu verbinden. Die Analogie dieses Entwurfsmusters zu einem Stromnetzadapter für Auslandsreisen ist naheliegend. Der Stromnetzadapter ermöglicht es, eine Steckdose mit einem Stecker für ein elektrisches Gerät zu verbinden, auch wenn der Stecker nicht direkt passt, weil er eine andere Schnittstelle aufweist.

In der Software-Entwicklung wird ein Adapter (engl. auch Wrapper) eingesetzt, wenn eine existierende Klasse verwendet werden soll, deren Interface nicht dem vorgesehenen Interface entspricht. Die existierende Klasse soll oder kann nicht verändert werden, insbesondere wenn sie Teil einer externen Bibliothek ist, die von Dritten entwickelt wird. Die zu adaptierende Klasse aus der Bibliothek ist häufig von Interesse, da sie komplexes Verhalten implementiert, das wiederverwendet werden soll. Das Prinzip der Wiederverwendung ist grundsätzlich effizient, wenn externe Bibliotheken in ein Projekt eingebunden werden, von denen bekannt ist, dass sie umfänglich getestet worden sind und dass sie sich in anderen Projekten bereits praktisch bewährt haben. Im Java-Umfeld werden Bibliotheken i.d.R. aus einem zentralen Repository über Build-Management-Werkzeuge wie Maven oder Gradle automatisiert in ein Projekt eingebunden.

Das Adapter-Muster gibt es in zwei Varianten: Objektadapter und Klassenadapter. In beiden Varianten wird die Ziel-Schnittstelle Target von der Adapter-Klasse Adapter implementiert. Weiterhin ruft der Adapter in der von Target vorgeschriebenen Methode operation die Methode service der adaptierten Klasse Adaptee auf. Es können natürlich auch mehrere Methoden adaptiert werden. Beim Objektadapter besteht eine Assoziation zwischen Adapter und adaptierter Klasse, während beim Klassenadapter der Adapter die adaptierte Klasse erweitert. Der Klassenadapter ist in Java nicht zu realisieren, falls Target kein Interface, sondern eine zustandsbehaftete Klasse ist, da in diesem Fall Mehrfachvererbung erforderlich wäre. Die folgenden UML-Klassendiagramme visualisieren Objekt- und Klassenadapter im Vergleich zueinander.

In dem folgenden Beispiel zur Motivation eines Adapters möchten wir die Fläche und den Umfang für unterschiedliche 2-dimensionale Formen bestimmen. Dazu haben wir das Interface Shape spezifiziert und bereits Implementierungen für einfache Formen wie Kreis, Rechteck und Dreieck entwickelt.

interface Shape {
    Double perimeter();
    Double area();
}
class Circle implements Shape {
    Point2D center;
    Double radius;

    Circle(Point2D center, Double radius) {
        this.center = center;
        this.radius = radius;
    }

    @Override
    public Double area() { return Math.PI * radius * radius; }

    @Override
    public Double perimeter() { return 2 * Math.PI * radius; }
}
class Rectangle implements Shape {
    Point2D topLeft;
    Double width, height;

    Rectangle(Point2D topLeft, Double width, Double height) {
        this.topLeft = topLeft;
        this.width = width;
        this.height = height;
    }

    @Override
    public Double perimeter() { return 2 * (width + height); }

    @Override
    public Double area() { return width * height; }
}
class Triangle implements Shape {
    Point2D p1, p2, p3; // points
    Double e1, e2, e3; // edges

    Triangle(Point2D p1, Point2D p2, Point2D p3) {
        this.p1 = p1; this.p2 = p2; this.p3 = p3;
        e1 = p1.distance(p2); e2 = p2.distance(p3); e3 = p3.distance(p1);
    }

    @Override
    public Double perimeter() { return e1 + e2 + e3; }

    @Override
    public Double area() {
        Double s = perimeter() / 2;
        return Math.sqrt(s * (s - e1) * (s - e2) * (s - e3));
    }
}

Das Interface Shape nutzen wir in unserer Anwendung (Client), um über verschiedene Formen zu iterieren und diese über ihre gemeinsame Schnittstelle zu verarbeiten. Es soll noch eine Shape-Implementierung für beliebige Polygone hinzugefügt werden. Ein Polygon besteht aus einer Reihe von Punkten, die über Kanten miteinander verbunden werden. Der Client soll zur Veranschaulichung die Fläche und den Umfang der folgenden 4 Formen berechnen.

Die Bestimmung der Fläche eines Polygons ist nicht trivial. Aus diesem Grund möchten wir die Bibliothek Apache Commons Math einbinden, die eine API für sehr viele mathematische Grundlagen bereitstellt. Wir interessieren uns für die Methode getSize der Klasse PolygonsSet, über die die Fläche für ein Polygon berechnet werden kann. Das Code-Beispiel zeigt, wie das Polygon über einen Objektadapter (PolygonObjectAdapter) oder alternativ über einen Klassenadapter (PolygonClassAdapter) realisiert werden kann. In der Klasse Client ist zu erkennen, dass auch die neuen Adapter-Klassen das Interface Shape erfüllen.

import org.apache.commons.math3.geometry.euclidean.twod.PolygonsSet;
import org.apache.commons.math3.geometry.euclidean.twod.Vector2D;

class PolygonObjectAdapter implements Shape {
    PolygonsSet adaptee;

    PolygonObjectAdapter(Point2D... points) {
        Vector2D[] vectors = Stream.of(points).map(p -> new Vector2D(p.getX(), p.getY())).toArray(size -> new Vector2D[size]);
        adaptee = new PolygonsSet(.1, vectors);
    }

    @Override
    public Double perimeter() { return adaptee.getBoundarySize(); }

    @Override
    public Double area() { return adaptee.getSize(); }
}
import org.apache.commons.math3.geometry.euclidean.twod.PolygonsSet;
import org.apache.commons.math3.geometry.euclidean.twod.Vector2D;
	
class PolygonClassAdapter extends PolygonsSet implements Shape {

    PolygonClassAdapter(Point2D... points) {
        super(.1, Stream.of(points).map(p -> new Vector2D(p.getX(), p.getY())).toArray(size -> new Vector2D[size]));
    }

    @Override
    public Double perimeter() { return this.getBoundarySize(); }

    @Override
    public Double area() { return this.getSize(); }
}
class Client {

    public static void main(String[] args) {
        Shape s1 = new Rectangle(new Point2D(0, 0), 3., 2.);
        Shape s2 = new Triangle(new Point2D(0, 0), new Point2D(0, 2), new Point2D(1, 1));
        Shape s3 = new PolygonObjectAdapter(new Point2D(0, 0), new Point2D(3, 0), new Point2D(3, 2), new Point2D(0, 2), new Point2D(1, 1));
        Shape s4 = new PolygonClassAdapter(new Point2D(0, 0), new Point2D(3, 0), new Point2D(2, 1), new Point2D(3, 2), new Point2D(0, 2), new Point2D(1, 1));

        List.of(s1, s2, s3, s4).forEach(s -> {
            System.out.printf("%s: perimeter = %.2f, area = %.2f\n", s.getClass().getSimpleName(), s.perimeter(), s.area());
        });
    }
}

Die gezeigten Code-Beispiele zum Entwurfsmuster Adapter finden sich im Verzeichnis /patterns/adapter des Modul-Repository.