Zum Inhalt springen

Schnittstelle (Objektorientierung)

aus Wikipedia, der freien Enzyklopädie
(Weitergeleitet von Schnittstelle (Programmierung))

Eine Schnittstelle oder ein Interface (englisch interface oder protocol) definiert in der objektorientierten Programmierung, welche Methoden in den unterschiedlichen Klassen u. Ä. vorhanden sind oder vorhanden sein müssen. Sie legt die Namen und die Signatur der Methoden, also die Parametertypen und den Rückgabetyp, fest.

Technische Details

[Bearbeiten | Quelltext bearbeiten]

Eine Schnittstelle gibt an, welche Methoden vorhanden sind oder vorhanden sein müssen. Zusätzlich zu dieser syntaktischen Definition sollte ein so genannter Kontrakt über die Bedeutung (Semantik) definiert werden, festgelegt als Vorbedingungen und Nachbedingungen der verschiedenen Methoden. Der Kontrakt wird meist nur informell in der Dokumentation oder einer externen Spezifikation der Schnittstelle festgelegt. Es stehen auch formelle Spezifikationssprachen wie z. B. die OCL zur Verfügung. Einige Programmiersprachen, z. B. Eiffel, bieten auch unmittelbar syntaktische Möglichkeiten zur Festlegung eines Kontrakts.

Schnittstellen stellen eine Garantie über die in einer Klasse vorhandenen Methoden dar. Sie geben an, dass alle Objekte, die diese Schnittstelle besitzen, gleich behandelt werden können (Polymorphie).

In einigen Programmiersprachen, die keine Mehrfachvererbung unterstützen, z. B. Java, können Schnittstellen verwendet werden, um Kompatibilitäten zwischen Klassen zu definieren, die nicht voneinander erben: Die Schnittstellenbeziehungen sind nicht an den strengen Klassenbaum gebunden. Dazu werden Schnittstellendeklarationen häufig explizit als solche markiert (etwa mit dem Schlüsselwort interface). Als Ersatz für Mehrfachvererbung eignen sich Schnittstellen allerdings nur eingeschränkt, da sie lediglich Methoden und deren Parameter definieren und keine direkte Vererbung von Funktionalität ermöglichen.

In Java gibt es mittlerweile die Möglichkeit, in Schnittstellen Methoden tatsächlich zu implementieren und als Standard (default) zu markieren. Dies ermöglicht die Definition von Funktionalität, die Klassen „erben“, die das Interface nutzen. Java-Interfaces entwickeln sich dadurch etwas näher in Richtung der Mehrfachvererbung.

Andere Programmiersprachen, die Mehrfachvererbung unterstützen, zum Beispiel C++, kennen zwar das Konzept von Schnittstellen, behandeln diese aber wie gewöhnliche Klassen. Man spricht dann auch von abstrakten Klassen. Manchmal wird auch eine eigene Sprache (eine sogenannte Schnittstellenbeschreibungssprache, IDL) zur Deklaration der Schnittstelle verwendet – meist ist das bei Middleware-Systemen wie CORBA oder DCOM der Fall. Objektbasierte Sprachen ohne strenge Typisierung kennen meist keine Schnittstellen.

Definition von Konstanten

[Bearbeiten | Quelltext bearbeiten]

In einigen Programmiersprachen wie Java oder PHP ist es möglich, Konstanten innerhalb einer Schnittstellendefinition zu deklarieren. Allen implementierenden Klassen stehen dann diese Konstanten zur Verfügung.

Im folgenden Beispiel in C# wird eine Schnittstelle Face mit den Methoden Move(...), Turn(...), Scale(...), GetArea(), SetColor(...), GetColor() deklariert. Die Klasse Polygon implementiert die Schnittstelle Face, was in C# mit der Notation Polygon : Face festgelegt wird. Die Klasse Polygon muss daher alle diese Methoden mit den angegebenen Parametertypen und Rückgabetypen implementieren. Die implementierten Methoden haben den Zugriffsmodifikator public.

public interface Face
{
	void Move(float x, float y);
	void Turn(float x, float y, double angle);
	void Scale(float factor);
	double GetArea();
	void SetColor(Color color);
	Color GetColor();
}



public class Polygon : Face
{
	private List<PointF> points;
	private Color color;
	
	public Polygon(List<PointF> points, Color color)
	{
		this.points = points;
		this.color = color;
	}
	
	public void Move(float x, float y)
	{
		for (int i = 0; i < points.Count; i++)
		{
			PointF point = points[i];
			points[i] = new PointF(point.X + x, point.Y + y);
		}
	}
	
	public void Turn(float x, float y, double angleInDegrees)
	{
		double angleInRadians = angleInDegrees * (Math.PI / 180);
		double cosine = Math.Cos(angleInRadians);
		double sine = Math.Sin(angleInRadians);
		for (int i = 0; i < points.Count; i++)
		{
			PointF point = points[i];
			points[i] = new PointF((float) (cosine * (point.X - x) - sine * (point.Y - y) + x), (float) (sine * (point.X - x) + cosine * (point.Y - y) + y));
		}
	}
	
	public void Scale(float factor)
	{
		for (int i = 0; i < points.Count; i++)
		{
			PointF point = points[i];
			points[i] = new PointF(factor * point.X, factor * point.Y);
		}
	}
	
	public double GetArea()
	{
		double area = 0.0;
		for (int i = 0; i < points.Count; i++)
		{
			PointF point1 = points[i];
			PointF point2 = points[(i + 1) % points.Count];
			area += (point1.Y + point2.Y) * (point1.X - point2.X);
		}
		return Math.Abs(area / 2.0);
	}
	
	public void SetColor(Color color)
	{
		this.color = color;
	}
	
	public Color GetColor()
	{
		return color;
	}
}

Bemerkungen: Die hier gezeigte Implementierung der Methode Turn(...) verwendet im Prinzip die Drehmatrix der Ebene. Die Implementierung der Methode GetArea() verwendet die Gaußsche Trapezformel.

Existiert beispielsweise eine Schnittstelle Konto mit der Methode abbuchen(), müssen alle Klassen, die diese Schnittstelle implementieren, über eine Methode abbuchen verfügen. Weiteres Beispiel: Eine Anzahl an Klassen mit den Namen SparKonto, GiroKonto und DepotKonto implementieren die Schnittstelle Konto. Die Schnittstelle hat eine Methode getKontostand, also müssen alle Klassen mit der Schnittstelle die Methode getKontostand bereitstellen.

public interface Konto
{
    int getKontostand(); // abstrakte Signatur-Definition; implizit public
}

public class SparKonto implements Konto
{
    private int kontostand;
    // Implementierung der durch Konto definierten Methode
    public int getKontostand()
    {
        return kontostand;
    }
    
}

Von Schnittstellen lassen sich keine Objekte anlegen. Eine Instanziierung der Form new Konto() wäre im obigen Beispiel demnach unzulässig.[1]

Sofern keine explizite Qualifizierung erfolgt, sind jegliche in einer Schnittstelle deklarierte Methoden automatisch public, Variablen hingegen public, static und final. Im Gegensatz zu Klassen können Schnittstellen keine Konstruktoren enthalten; sie lassen sich schließlich nicht instanziieren. Dafür ist eine Erweiterung durch eine beliebige Anzahl an Basisschnittstellen möglich, wobei analog zum Ableiten von Klassen das Schlüsselwort extends verwendet wird.[1]

Der Sonderfall, dass eine Klasse zwei Schnittstellen mit derselben Methode implementiert, bedarf lediglich einer einzigen Überschreibung, die anschließend bei polymorpher Verwendung über beide Schnittstellen hinweg herangezogen wird. Voraussetzung dafür ist sowohl die gleiche Signatur (Bezeichner und Parameterliste) als auch derselbe Rückgabetyp.[2]

Klassen, die eine bestimmte Schnittstelle nicht explizit implementieren, aber die Bedingungen zur Überschreibung der darin deklarierten Methode erfüllen, sind durch nachträgliche Implementierung dieser Schnittstelle in einer abgeleiteten Klasse polymorph verwendbar:[3]

class A {
    public void foo() {
        System.out.println("Executing foo.");
    }
}

interface I {
    void foo();
}

class B extends A implements I { }

class C {
    public void bar() {
        I i = new B();
        i.foo(); // Ausgabe: "Executing foo."
    }
}

Des Weiteren ermöglichen die mit Java SE 8 eingeführten Default-Methoden, vordefinierte Funktionalitäten innerhalb einer Schnittstelle bereitzustellen. Eine Überschreibung in implementierenden Klassen ist dabei möglich, im Gegensatz zu abstrakten Methoden jedoch nicht verpflichtend.[1][4]

Um sich explizit auf Default-Methoden einer implementierten Schnittstelle zu beziehen, besteht die Notation MyInterface.super, die einen expliziten Verweis auf den Namensraum der Schnittstelle MyInterface ermöglicht. Diese Syntax ist beim Aufruf der ursprünglichen Variante einer überschriebenen Default-Methode erforderlich.[5]

Treten Ambiguitäten infolge mehrfacher Vererbung nicht-abstrakter Methoden aus einer Basisklasse und einer Schnittstelle auf, wird der Konflikt zugunsten der Klassenmethode aufgelöst: Sie hat Vorrang gegenüber der gleichnamigen Default-Methode. Ein Namenskonflikt durch mehrere gleichartige Default-Methoden mit identischer Signatur aus verschiedenen Schnittstellen führt hingegen zu einem Compilerfehler, insofern nicht explizit eine Überschreibung erfolgt. (Hierbei lässt sich die obige Syntax MyInterface.super zum Aufruf einer der beiden Default-Methoden anwenden.)[6]

Analog zu Klassen können Schnittstellen generisch sein.[1]

Java unterscheidet sich in einem weiteren Punkt gegenüber .NET-Sprachen darin, dass eine Klasse, die ein Interface implementiert, nicht explizit deklariert werden muss. Das folgende Beispiel definiert eine sogenannte anonyme innere Klasse innerhalb einer Methode.

public Konto erstelleKonto()
{
    return new Konto()
    { //Beginn der anonymen inneren Klasse
        // Implementierung der durch Konto definierten Methode
        public int getKontostand()
        {
            return 0;
        }
    }; //Ende der anonymen inneren Klasse
}

Eine Schnittstelle, die genau eine abstrakte Methode deklariert, wird als funktional bezeichnet. Solche Schnittstellen dienen als Grundlage für die Verwendung von Lambda-Ausdrücken und Methodenreferenzen. Der Compiler leitet hierbei aus dem Kontext ab, welche funktionale Schnittstelle implementiert wird und welche Methode durch den Lambda-Ausdruck oder die Methodenreferenz zu überschreiben ist. Mit @FunctionalInterface ist eine Annotation der Standardbibliothek gegeben, die Schnittstellen explizit auf Funktionalität validiert.[1][7]

Namenskonventionen

[Bearbeiten | Quelltext bearbeiten]

In einigen Programmiersprachen ist es üblich, Schnittstellen durch besondere Präfixe oder Suffixe erkennbar zu machen (Ungarische Notation). So wird häufig ein „I“ (für Interface) vorangestellt oder ein „IF“ beziehungsweise „Interface“ angehängt. Die oben angeführte Beispielschnittstelle Konto hieße dann IKonto, KontoInterface oder KontoIF.

Vorteile:

  • Schnittstellen sind am Namen als solche erkennbar.
  • Implementierende Klassen können einen einfacheren Namen haben.

Nachteile:

  • Schnittstellen sollten nicht am Namen als solche erkannt werden, da man als Verwender anderer Objekte immer nur deren Schnittstelle (also öffentliche Methoden) bedenken sollte.[8]
  • Schnittstellen können als das wesentliche Element der Programmierung betrachtet werden. Daher ist es sinnvoller, die Namen der Implementierungen mit Präfixen oder Suffixen zu ergänzen.
  • Schnittstellen sind besonders dann sinnvoll, wenn es mehr als eine Implementierung gibt, sodass die implementierenden Klassen ohnehin mit Präfixen und Suffixen benannt werden.

Schnittstellen, die allgemeingültige Fähigkeiten oder Zustände beschreiben, werden insbesondere in der Java-Standardbibliothek häufig durch adjektivische Bezeichner wie Serializable oder Comparable<T> benannt.

Einzelnachweise

[Bearbeiten | Quelltext bearbeiten]
  1. a b c d e Chapter 9. Interfaces. Abgerufen am 6. Juli 2025.
  2. Christian Ullenboom: Java ist auch eine Insel: Einführung, Ausbildung, Praxis (= Rheinwerk Computing). 16., aktualisierte und überarbeitete Auflage, 1., korrigierter Nachdruck 2023. Rheinwerk Verlag, Bonn 2023, ISBN 978-3-8362-8745-6, S. 528 f.
  3. Christian Ullenboom: Java ist auch eine Insel: Einführung, Ausbildung, Praxis (= Rheinwerk Computing). 16., aktualisierte und überarbeitete Auflage, 1., korrigierter Nachdruck 2023. Rheinwerk Verlag, Bonn 2023, ISBN 978-3-8362-8745-6, S. 531.
  4. What's New in JDK 8. Archiviert vom Original am 1. Juli 2025; abgerufen am 13. Juli 2025 (amerikanisches Englisch).
  5. Christian Ullenboom: Java ist auch eine Insel: Einführung, Ausbildung, Praxis (= Rheinwerk Computing). 16., aktualisierte und überarbeitete Auflage, 1., korrigierter Nachdruck 2023. Rheinwerk Verlag, Bonn 2023, ISBN 978-3-8362-8745-6, S. 542 f.
  6. Christian Ullenboom: Java ist auch eine Insel: Einführung, Ausbildung, Praxis (= Rheinwerk Computing). 16., aktualisierte und überarbeitete Auflage, 1., korrigierter Nachdruck 2023. Rheinwerk Verlag, Bonn 2023, ISBN 978-3-8362-8745-6, S. 541–543.
  7. FunctionalInterface (Java Platform SE 8 ). Abgerufen am 13. Juli 2025.
  8. Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides: Design Patterns. Elements of Reusable Object-Oriented Software. Addison-Wesley, 1995, ISBN 0-201-63361-2, Seite 18