Was sind Interfaces und wofür werden sie wirklich gebraucht?

Autor: Sergej Kukshaus Veröffentlicht: vor 8 monaten Lesedauer: ca. 7 Minuten

Bevor wir anfangen, möchte ich ganz kurz erwähnen, was ein Interface überhaupt ist.

Gibst du das Wort bei Duden ein, kommt prompt die Antwort:

Spezielle Schaltung zur elektronischen Anpassung zweier sonst inkompatibler Geräte oder Geräteteile

Sagen wir mal so, es ist nicht falsch, was da steht. Es ist aber nicht so 100%ig richtig 🙂

Als Zweites steht da „Schnittstelle„. Das ist wiederum goldrichtig. Hierfür wird jedoch als Beschreibung folgendes angegeben:

Verbindungsstelle zwischen Funktionseinheiten eines Datenverarbeitungs- oder -übertragungssystems, an der der Austausch von Daten oder Steuersignalen erfolgt

Also entweder bin ich zu blöde um hier den Zusammenhang zu erkennen oder dieser Satz ergibt genauso viel Sinn wie der obere.

Ok, Duden, jetzt probiere ich es mal:

Definiert einen Mindestsatz an Berührungspunkten zwischen zwei verschiedenen Systemen.

Na gut. Ich gebe es ja zu, es ist nicht so einfach „interface“ mit einem Satz zu erklären und dabei noch allgemein zu bleiben.

Jedoch bin ich nicht für das allgemeine blah blah bekannt, sondern versuche alles so einfach und konkret wie nur möglich darzustellen 🙂

Was ist ein Interface?

Eigentlich ist es ganz einfach.

Stell dir mal vor, du möchtest deine Haustiere füttern. Du hast einen Hund und eine Katze. Und wenn nicht, auch nicht schlimm 🙂

Wenn du das jetzt in Quellcode Ausdrücken würdest, könnte das so aussehen:

public void HundFüttern(Hund hund)
{
	Dose dose = _kühlschrank.DoseHerausholen(katze.Lieblingsfutter);
	dose.Öffnen();
	dose.DoseninhaltEinfüllenIn(hund.Napf);
	dose.Wegwerfen();
	
	Rufen(hund);
}

public void KatzeFüttern(Katze katze)
{
	Dose dose = _kühlschrank.DoseHerausholen(katze.Lieblingsfutter);
	dose.Öffnen();
	dose.DoseninhaltEinfüllenIn(katze.Napf);
	dose.Wegwerfen();
	
	Rufen(katze);
}

Und was stellen wir fest?

Genau, ist genau dasselbe. Und wenn du noch ein paar mehr Haustiere hast, muss das noch einmal kopiert werden.

Man wird hierdurch also extremst eingeschränkt und neigt zu Wiederholungen.

Besser ist es doch sowas hier zu haben:

public interface Haustier
{
	Napf Napf { get; set; }
	Futter Lieblingsfutter { get; set; }
}

public void Füttern(Haustier tier)
{
	Dose dose = _kühlschrank.DoseHerausholen(tier.Lieblingsfutter);
	dose.Öffnen();
	dose.DoseninhaltEinfüllenIn(tier.Napf);
	dose.Wegwerfen();
	
	Rufen(tier);
}

Damit haben wir schon mal viele Möglichkeiten abgedeckt.

Viele? Warum nur „viele“ und nicht alle?

Nun ja, ganz einfach: So ein Goldfisch hat nun einmal kein Futternapf 🙂

Und großen „Haustieren“, wie z.B. Pferden, Kühen, etc. haben auch kein Napf. Höchstens ein Trog. Und da wird auch kein Kühlschrank aufgemacht um eine Dose herauszuholen.

Also müssen wir noch einmal unterscheiden:

class Hund : HaustierMitNapf { }
class Goldfisch : UnterwasserHaustier { }
class Pferd : HaustierMitTrog { }

Auf diese Art und Weise, können die Tierchen alle was zu essen bekommen:

me.Füttern(bella);
me.Füttern(guppy);
me.Füttern(blacky);

Wie du ein Interface erstellst

interface IAmAnInterface 
{
	string Content { get; set; }
	void DoSomething();
}

Laut den Kodierrichtlinien von Microsoft, hat es sich bei C# eingebürgert, dass ein interface mit dem Buchstaben I beginnt.

Für jemanden, der aus der Java- oder PHP-Welt kommt, ist das auf den ersten Blick zunächst ungewohnt. Jedoch gewöhnt man sich ziemlich schnell daran.

Jetzt kann man damit mit der Namensgebung etwas „spielen“ und das I als ein englisches Wort betrachten. Dann entstehen solche Namen wie z. B. ICanBeClosed oder auch IDestroyEverything.

Man kann sich auch an die Microsoft’s Strategie halten und die Interfaces nach Eigenschaften benennen. Beispielsweise: ICloseable oder IDestroyable. Geht auch, ist nur nicht so „lustig“ 🙂

Innerhalb der Schnittstelle (bzw. Interface), kannst du deine Methoden definieren.

Nur definieren, nicht implementieren. Soll heißen, in einem Interface gibt es keine Logik.

Okay, das ist nicht so ganz richtig. Ab C# 8.0 hat Microsoft nämlich ein weiteres Feature implementiert, welches die Welt nicht braucht: Standardschnittstellenmethoden. Oder auch: Default implementations.

Wahrscheinlich ist es direkt nach dem var, das schlechteste Feature in C# überhaupt. Hoffentlich werden es nicht so viele Leute verwenden wie war var.

Ich persönlich, kenne keinen einzigen Fall, bei dem ich das bisher vermisst habe. Wenn ich es doch benötige, dann schalte ich eine abstrakte Klasse dazwischen.

Aber bei einer Schnittstelle erwarte ich, dass ich auf gar keinen Fall eine Art Logik habe.

Wann sollte man ein Interface verwenden?

Grundsätzlich gilt immer:

So abstrakt wie möglich, so konkret wie nötig.

Und das gilt sowohl für die Erstellung als auch für die Verwendung.

Ein Interface sollte auch nicht komplett überladen sein.

Ich sehe immer wieder Interfaces, mit Methodendefinitionen im zweistelligen Bereich. Wer soll die den bitte alle implementieren? Selbst Microsoft macht das in dem dotNET Framework.

Interfaces sollen klein gehalten werden.

Solche Interfaces sind schon bei der Erstellung zum Scheitern verurteilt und werden nur im äußersten Notfall eingebunden.

Das dotNET Framework bietet bereits einige Interfaces, die man verwenden kann.

Hat man sich zwei Hunde besorgt, kann man das beispielsweise so schreiben.

class Person
{
	List<Hund> Hunde { get; set; }
}

Besser ist es jedoch, wenn man das abstrahiert und wie folgt schreibt:

class Person
{
	IEnumerable<Haustier> Haustiere { get; set; }
}

Was haben wir daraus gewonnen?

  1. Unsere Liste kann jetzt neben Hunden auch Katzen, Hamster und Goldfische beinhalten.
  2. Wir sind nicht mehr auf eine Liste beschränkt. Es kann auch ein Array sein.

Auch Properties und Methodenparameter als Interface definieren.

Wie du Interfaces gezielt einsetzt

Sicher hast du auch schon einmal gehört, dass es zum guten Ton gehört immer ein Interface zu verwenden.

Always implement against an interface.

Grundsätzlich ist es auch richtig.

Ich persönlich finde jedoch, man soll damit auch nicht unbedingt übertreiben.

Mehrschichten-Architektur

In einer klassischen Mehrschichten-Architektur, gibt es genau drei Schichten:

GUI – Schicht / UI Layer:

Hier werden die Daten angezeigt und Befehle aus der unteren Schicht ausgeführt. Ansonsten gibt es hier keine spezifische Logik.

In die GUI – Schicht können unter Offline-, Online-, Mobile-Applikationen aber auch Unit-Test-Module vorhanden sein.

Ja, richtig gehört, für mich gehört so ein Test-Projekt auch zur GUI – Schicht.

Geschäftslogik-Schicht / Business Layer:

Dies ist der Kern der Applikation. Hier werden die meisten Anfragen verarbeitet und die Daten sowohl zur Anzeige als auch zur Speicherung aufbereitet.

Datenschicht / Data Layer:

In dieser Schicht werden die Daten aus diversen Quellen entweder gespeichert oder auch gelesen. Sonst nichts.

Dabei gehen die Abhängigkeiten von oben nach unten. Der Datenschicht kann es vollkommen egal sein, von wo aus diese aufgerufen wird. Es dürfen auf keinen Fall Abhängigkeiten zur UI bestehen.

Während in den unteren beiden Schichten mindestens 90% aller Klassen durch Schnittstellen abgedeckt werden sollen, können diese in der GUI Schicht ruhig auf 0% reduziert werden.

Interfaces in der Datenschicht

Die Datenschicht besteht häufig Modulkombinationen zwischen Datenmodell und Datenhaltung.

Modellklassen (auch Value Objects genannt), benötigen kein Interface. Solche Modellklassen sind ganz einfache Datentypen und beinhalten ausschließlich Eigenschaften.

class Person
{
	public string Name { get; set; }
	public int Alter { get; set;}
}

interface IPersonRepository
{
	Person GetById(int id);
}

class DapperPersonRepository : IPersonRepository
{
	private IDbConnection _dataContext;
	
	public DapperPersonRepository(IDbConnection dataContext)
	{
		_dataContext = dataContext;
	}
	
	public Person GetById(int id)
	{
		string sqlQuery = "SELECT * FROM Persons WHERE id = @id";
		_dataContext.Query<Person>(sqlQuery, new { id });
	}
}

Aber auch hier kann man sich ein Interface spendieren. Tue dir bloß selbst ein Gefallen und mache das nicht für jedes dieser Datenmodelle.

Es reicht vollkommen aus, wenn du an dieser Stelle ein Interface IEntity erstellst und dieses in jeder Modellklasse verwendest.

Alles andere (in den meisten Fällen Repositories) benötigen eigene Schnittstellen.

Interfaces in der Geschäftslogik

Auch hier gibt es Ausnahmen.

Es gibt wiederum einige Hilfsklassen, die nur Daten halten bzw. transportieren. Diese müssen auch nicht unbedingt eine Schnittstellendefinition haben.

Alles andere (und das ist das meiste) beinhaltet wiederum Schnittstellen.

class AfterPersonCreatedMessage 
{
}

interface IPersonManager
{
	void Create(Person person);
}

class PersonManager : IPersonManager
{
	private IPeronRepository _repository;
	private IEventBroker _eventBroker
	
	public PersonManager(IPeronRepository repository, IEventBroker eventBroker)
	{
		_repository = repository;
		_eventBroker = eventBroker;
	}
	
	public void Create(Person person)
	{
		_repository.Insert(person);
		_eventBroker.Raise<AfterPersonCreatedMessage>();
	}
}

Interfaces in der GUI Schicht

In der GUI – Schicht läuft es ein wenig anders ab.

Auf der GUI Schicht, braucht kein Schwein Interfaces.

Ehrlich nicht.

interface IBillingViewModel 
{
	void Create(Bill bill);
}

class BillingViewModel : IBillingViewModel
{
	private IBillingManager _billingManager;
	
	public BillingViewModel(IBillingManager billingManager)
	{
		_billingManager = billingManager;
	}
	
	public void Create(Bill bill)
	{
		_billingManager.Create(bill);
	}
}

Das ist Mist. Also bitte so nicht nachmachen 🙂

ViewModel (MVVM) kannst du hier auch gegen Controller (MVC) austauschen. Es ist genau dasselbe.

Die Hauptfrage ist doch, wofür benötigt man ein Interface?

Für Austauschbarkeit und Flexibilität.

In der Praxis sieht es so aus: Ändert sich die GUI, ändern sich auch die ViewModels und somit auch die Interfaces.

Interfaces vs. Abstrakte Klassen

Im Grunde genommen sind reine abstrakte Klassen, nichts anderes als Interfaces. Doch, ein Unterschied haben abstrakte Klassen:

Es ist mehr Quellcode nötig.

Abstrakte Klasse:

public abstract class MyAbstractClass
{
	public abstract void DoSomething();
}

public class MyConcreteClass : MyAbstractClass
{
	public override void DoSomething()
	{
		// ...
	}
}

Interface:

public interface IAmAnInterface
{
	void DoSomething();
}

public class MyConcreteClass : IAmAnInterface
{
	public void DoSomething()
	{
		// ...
	}
}

Man beachte die jeweiligen Schlüsselwörter, die dazugekommen sind.

Halten wir fest: Abstrakte Klassen sind da, um den Quellcode unnötig aufblähen zu lassen.

Das ist natürlich Quatsch 🙂

Abstrakte Klassen haben den Interfaces gegenüber einen riesengroßen Vorteil: Es sind Klassen! Unglaublich aber war 🙂

Und was können Klassen? Richtig! Logik beinhalten. Interfaces haben nämlich keine Logik.

Jedoch haben Interfaces einen weiteren Vorteil: Eine Klasse kann mehrere davon definieren (genauer genommen: implementieren). Jedoch nur von einer einzigen Klasse erben.

Fazit

Fassen wir noch einmal kurz zusammen:

Es gibt Interfaces und es gibt das „Interface-Prinzip“. Während das Interface nur ein Schlüsselwort aus vielen Programmiersprachen ist, ist das Prinzip so definiert, dass man andere Klassen in (beispielsweise) Interfaces aufteilt und nur mit solchen arbeitet.

Wenn du bereits viele vernünftige Interfaces definiert hast, kannst du dir anschließend deine Klasse relativ einfach auf Basis von Interfaces aufbauen. Genaue Implementierung, spielt jedoch keine übergeordnete Rolle.

Sei der erste und teile deine Meinung mit der Welt!
... und was meinst du dazu?
Deine E-Mail-Adresse wird nicht veröffentlicht.