Was sind Interfaces?

the next big thing Golo Roden  –  42 Kommentare

Interfaces sind eines der wichtigsten Konstrukte in der Programmierung, um Code sauber zu strukturieren, weshalb sie auch als Grundlage für viele Entwurfsmuster dienen. Doch was genau sind Interfaces und warum sind sie so relevant?

Ein Begriff, der im Entwurf und der Entwicklung von Software regelmäßig auftaucht, ist der des "Interface". Wenn man danach fragt, was ein Interface genau ist, stellt man fest, dass es hierauf zwei Antworten gibt – eine konzeptionelle und eine technische. Während die eine allgemeingültig ist, ist die andere häufig geprägt von der Art, wie verschiedene Programmiersprachen Interfaces implementieren.

Konzeptionell gesehen beschreiben Interfaces die äußere Form. Das heißt, sie beziehen sich auf Syntax. Sie beschreiben aber auch Semantik, da mit ihnen untrennbar auch die Beschreibung von Verhalten verknüpft ist. Das bedeutet, letzten Endes geht es um die Standardisierung von Kommunikation zwischen zwei Parteien, insofern ist ein Interface einem Protokoll in gewissem Sinne ähnlich.

Wird ein Interface beispielsweise von einer Klasse implementiert, muss die Klasse alle Vorgaben des Interfaces umsetzen, das heißt, alle Methoden mit den entsprechenden Signaturen bereitstellen. Eine Klasse kann dabei nicht nur ein Interface implementieren, sondern durchaus mehrere, sodass sich deren Aspekte flexibel kombinieren lassen.

Eines der Ziele von Interfaces ist dabei die Austauschbarkeit konkreter Instanzen, unabhängig von deren Typ: Solange beide Instanzen demselben Interface genügen und die Verwenderin nur das Interface kennt, wird die jeweilige Implementierung zu einer Blackbox, die transparent getauscht werden kann. Insofern trennen und entkoppeln Interfaces den Kontrakt und die Implementierung.

Was sind Interfaces?

Interfaces unterschiedlich implementieren

Trotz der einfachen und klaren Definition implementieren verschiedene Programmiersprachen Interfaces auf unterschiedliche Art. Jede Programmiersprache, die Interfaces unterstützt, bietet sozusagen eine eigene Interpretation der konzeptionellen Definition. In vielen Fällen hängt das mit dem zugrunde liegenden Typsystem zusammen.

C# beispielsweise verfügt über ein statisch-nominales Typsystem. Das heißt, die Beziehungen zwischen den Typen müssen explizit deklariert werden. Deshalb gibt es in C# Schlüsselwörter wie interface und implements, um Interfaces ausdrücklich definieren und implementieren zu können. Wer nur C# kennt, wird dadurch rasch zu der Annahme verleitet, das müsse zwingend so sein.

Dass es auch anders geht, zeigen zum Beispiel TypeScript und Go: Beide verfügen über ein statisch-strukturelles Typsystem, bei dem die Kompatibilität von Typen implizit durch eine gleichartige Struktur hergestellt wird. Interfaces werden in diesen Sprachen dadurch implementiert, dass Typen geschaffen werden, die zu den Interfaces passen – ohne den Zusammenhang ausdrücklich spezifizieren zu müssen.

Interessant ist, dass Interfaces in C# auch nur für Klassen verwendbar sind, in anderen Sprachen auch durchaus für andere Typen, beispielsweise für Objekte oder gar für Funktionen. Da Interfaces letztlich Syntax und Semantik definieren, liegt das eigentlich nahe – die Objektorientierung, die C# primär prägt, kennt aber keine Funktionen außerhalb von Klassen, weshalb Interfaces für eigenständige Funktionen dort nicht vorgesehen sind.

Interfaces unterschiedlich implementieren

Interfaces in Entwurfsmustern

Besonders häufig tauchen Interfaces im Kontext von Entwurfsmustern auf. Es gibt nur wenige dieser Muster, die ohne die Verwendung von Interfaces auskommen. Der Grund dafür ist, dass Entwurfsmuster dazu dienen, Software besser zu strukturieren, und deshalb häufig darauf abzielen, Bereiche zu entkoppeln. Genau diese Aufgabe übernehmen auch Interfaces – weshalb sie eine ideale Grundlage für Entwurfsmuster sind.

Das zeigt sich sehr schön in Entwurfsmustern wie der Factory oder der Strategy, die beide eine konkrete Implementierung von der Intention entkoppeln. Im einen Fall geht es dabei um Klassen, im anderen um Funktionen, aber die grundsätzliche Idee ist ähnlich. Für den Verwender einer Factory oder einer Strategy bleibt die konkrete Implementierung dadurch eine Blackbox. Es werden lediglich syntaktische Kompatibilität und eine gewisse Semantik zugesichert.

Interfaces in Entwurfsmustern

Interfaces für Komponenten, Services & Co.

Eine ähnliche Abstraktion findet auch beim Einsatz von Interfaces für Komponenten und Services statt. Auch hier wird die Kommunikation zwischen Komponente beziehungsweise Service auf der einen und dem Verwender auf der anderen Seite standardisiert, auch hier geht es um syntaktische Kompatibilität und semantische Versprechen.

Im Kontext von Services spricht man dann häufig von Application Programming Interfaces (APIs), und dort zeigt sich auch rasch die Nähe von Interfaces zu Protokollen. Insbesondere Protokolle wie GraphQL oder gRPC definieren eben nicht nur syntaktische Eigenheiten der Aufrufe, sondern versprechen auch eine gewisse Semantik beziehungsweise befördern eine Beschreibung der Semantik des Dienstes.

Auch für Service greift dabei die Grundidee der Entkopplung und Abstraktion. Denn Services lassen sich – wenn sie hinter Interfaces verborgen werden – unabhängig entwickeln, sodass zum Beispiel die verwendeten Technologien nicht systemrelevant werden. Lediglich die eingesetzten Protokolle müssen bleiben, die tatsächliche Implementierung ist austauschbar.

Interfaces für Komponenten, Services & Co.

Intention versus Implementierung

Wichtig zu verstehen ist bei alledem, dass es bei Interfaces (und auch bei Entwurfsmustern) im Wesentlichen um die Beschreibung der Intention geht, nicht um die konkrete Implementierung. Zwar wird häufig eine beispielhafte Referenzimplementierung gezeigt, man darf diese aber nicht mit der Intention verwechseln. Leider passiert das insbesondere bei Entwurfsmustern verhältnismäßig häufig.

Das Entwurfsmuster Singleton hat beispielsweise die Intention, globale Artefakte dadurch zu vermeiden, dass sich von einer Klasse nur genau eine Instanz erzeugen lässt. Es geht also um die Einmaligkeit. Wie das in einer konkreten Programmiersprachen konkret umgesetzt wird, darüber sagt das Singleton an sich nichts aus.

Die gängige Referenzimplementierung für Sprachen der C-Familie basiert dann auf privaten Konstrukturen, statischen Methoden und statischen Feldern. Was nun leider allzu häufig passiert, ist, dass diese Umsetzung mit der Intention verwechselt wird. Das Ergebnis sind dann falsche Schlüsse und darauf basierend Fehleinschätzungen wie: "JavaScript ist eine schlechte Programmiersprache, denn sie bietet keine privaten Konstruktoren, weshalb man noch nicht einmal Singletons implementieren kann."

Tatsächlich lassen sich Singletons in JavaScript sehr wohl definieren (und das sogar auf ausgesprochen elegantem Wege, verglichen mit C++, C# und Co.), allerdings funktioniert der Mechanismus hier gänzlich anders. Die Intention des Entwurfsmusters lässt sich aber auch in JavaScript problemlos abbilden – man muss das Singleton als Muster nur richtig lesen und interpretieren.

Patterns: Intention versus Implementierung

Fazit

Interfaces sind ein häufig unterschätztes, aber äußerst mächtiges und vielseitiges Konstrukt von Programmiersprachen. Deshalb ist es ausgesprochen wichtig, den Sinn von Interfaces zu verstehen und ihn vor allem auf konzeptioneller Ebene, das heißt unabhängig von einer konkreten Programmiersprache, zu begreifen.

Dabei hilft, wie so oft, der Blick über den Tellerrand und die Beschäftigung mit anderen Sprachen, um unterschiedliche Interpretationen kennenzulernen. Wer darauf achtet, Intention und Implementierung auseinander zu halten, hat dann sowohl auf der konkreten als auch auf der Metaebene einen großen Schritt zu besserem Code gemacht.