Interfaces
Kurz gesagt: Ein Interface ist ein Vertrag: eine Liste von Methoden ohne Rumpf, die jede Klasse schreiben muss, die es implementiert; eine Klasse kann mehrere Interfaces implementieren.
Problem
Gehälter und Rechnungen
Am Monatsende bezahlt die Firma zwei Arten von Dingen: die Gehälter der Mitarbeiter und die Rechnungen der Lieferanten. Du willst ein Array und eine Schleife: wer bezahlt wird, wie viel, und am Ende die Summe.
Ein Array Employee[] geht nicht: Eine Rechnung ist kein Mitarbeiter. Sie von Employee erben zu lassen ist falsch. Der Satz „eine Rechnung ist ein Mitarbeiter“ stimmt nicht, und die Rechnung bekäme auch einen Namen, ein Gehalt und eine Erhöhung.
Eine gemeinsame Basisklasse Payment geht auch nicht. Manager hat schon eine Basisklasse, Employee, und eine Klasse kann nur eine haben. Mitarbeiter und Rechnungen haben nichts gemeinsam, außer dass sie bezahlt werden können.
Die Idee
Ein Interface: ein Vertrag „kann etwas“
Ein Interface ist eine Liste von Methoden ohne Rumpf. Es sagt, was eine Klasse können muss, aber nicht wie:
interface IPayable{ int AmountDue();
string Payee();}class Invoice : IPayable{ public string Supplier { get; private set; } public int Amount { get; private set; }
public Invoice(string supplier, int amount) { Supplier = supplier; Amount = amount; }
public int AmountDue() { return Amount; }
public string Payee() { return Supplier; }}
Invoice power = new Invoice("Stromanbieter", 340);Console.WriteLine(power.Payee() + ": " + power.AmountDue() + " €");Stromanbieter: 340 €Ein Interface implementierst du mit demselben Doppelpunkt wie bei der Vererbung. Die Methoden schreibst du ohne override: Es gibt nichts zu überschreiben, weil das Interface keine eigenen Versionen hat.
In C# beginnen die Namen von Interfaces mit I: IPayable, IComparable, IDisposable. So zeigt class Invoice : IPayable sofort, dass der Doppelpunkt für ein Interface steht.
Die Methoden eines Interface sind immer public. Sie sind ein Versprechen an alle, also müssen sie auch in der Klasse public sein.
Sieh es dir an
Eine Schleife für alles, was bezahlt wird
Auch der Mitarbeiter nimmt den Vertrag an. Sein Betrag ist das Monatsgehalt:
class Employee : IPayable{ // ... Name, Salary, der Konstruktor, MonthlyPay()
public int AmountDue() { return MonthlyPay(); }
public string Payee() { return Name; }}Jetzt sind sowohl die Mitarbeiter als auch die Rechnungen IPayable. Ein Interface ist ein Typ: Es kann der Typ einer Variable, eines Parameters oder eines Arrays sein.
IPayable[] payments ={ new Employee("Ivan", 2000), new Manager("Maria", 3000, 500), new Invoice("Stromanbieter", 340), new Invoice("Vermieter", 1800)};int total = 0;foreach (IPayable p in payments){ Console.WriteLine(p.Payee() + ": " + p.AmountDue() + " €"); total += p.AmountDue();}Console.WriteLine("Gesamt: " + total + " €");Ivan: 2000 €Maria: 3500 €Stromanbieter: 340 €Vermieter: 1800 €Gesamt: 7640 €Der Manager schreibt nichts Neues. Von Employee erbt er sowohl den Vertrag als auch AmountDue(). Und AmountDue() ruft MonthlyPay() auf, das der Manager überschrieben hat. Deshalb bekommt Maria 3500 €.
IPayable[] payments ={ new Employee("Ivan", 2000), new Manager("Maria", 3000, 500), new Invoice("Stromanbieter", 340)};int total = 0;foreach (IPayable p in payments){ total += p.AmountDue();}Console.WriteLine("Gesamt: " + total + " €");Schritt für Schritt, in Worten
- Das Array ist vom Typ IPayable. Die Pfeile zeigen auf drei Klassen, aber sie erreichen nur ihren Vertrag.
- total beginnt bei 0.
- p zeigt auf den Mitarbeiter: AmountDue() gibt das Gehalt zurück, 2000.
- Der Manager ist ein Mitarbeiter, also kann auch er bezahlt werden. Gehalt plus Bonus: 3500.
- Die Rechnung ist kein Mitarbeiter, aber sie hält denselben Vertrag ein: 340.
- Die Schleife kennt die Klassen nicht. Sie weiß nur, dass jedes Objekt bezahlt werden kann.
Probier es aus
Wie hoch ist der Betrag?
Was gibt das Programm aus? Die Klassen sind aus der Karte oben.
IPayable p = new Manager("Maria", 3000, 500);Console.WriteLine(p.AmountDue());Manager ist ein Employee, und Employee implementiert den Vertrag. Also implementiert ihn auch der Manager, ohne es zu schreiben. Der Aufruf geht an die überschriebene Methode des Managers: 3000 + 500.
Die Idee
Eine Basisklasse, viele Verträge
Die Buchhaltung will die Rechnungen auch im CSV-Format: je eine Zeile „Lieferant;Betrag“. Das ist ein zweiter Vertrag. Eine Klasse kann beliebig viele Interfaces implementieren, durch Kommas getrennt:
interface IExportable{ string ToCsv();}
class Invoice : IPayable, IExportable{ // ... wie vorher
public string ToCsv() { return Supplier + ";" + Amount; }}IExportable[] forAccounting = { new Invoice("Stromanbieter", 340), new Invoice("Vermieter", 1800) };foreach (IExportable e in forAccounting){ Console.WriteLine(e.ToCsv());}Stromanbieter;340Vermieter;1800Hat die Klasse sowohl eine Basisklasse als auch Interfaces, kommt die Basisklasse zuerst:
class Manager : Employee, IExportableDer erste Name nach dem Doppelpunkt kann eine Klasse sein. Alle anderen sind Interfaces.
Es gibt eine Basisklasse und viele Verträge. So kann eine Klasse, deren Basisklasse schon vergeben ist, trotzdem mit ganz anderen Klassen in ein gemeinsames Array.
Probier es aus
Finde den Fehler
Wähle die Zeile mit dem Fehler.
public fehlt. Die Methoden eines Interface sind allen versprochen, und ohne Modifizierer ist die Methode versteckt. Der Compiler sagt, dass Invoice IPayable.AmountDue() nicht implementiert, weil die Methode nicht public ist.Probier es aus
Was fehlt?
class Invoice IPayable
{
// ...
}IPayable mit einem I vorne zeigt, dass es ein Interface ist.Die Idee
Abstrakte Klasse oder Interface?
Beides sind Basisklassen, von denen man keine Objekte erzeugt, und beide legen den Klassen darunter Pflichten auf. Der Unterschied liegt darin, was sie sagen:
| Abstrakte Klasse | Interface | |
|---|---|---|
| Sagt | „X ist ein Y“ | „X kann“ |
| Wie viele pro Klasse | eine | beliebig viele |
| Felder und Konstruktor | ja | nein |
| Gemeinsamer Code | ja | kaum |
- Eine abstrakte Klasse, wenn die Klassen darunter eine Art von Ding sind und Code und Daten teilen: Figuren haben einen Namen und eine Beschreibung, Mitarbeiter einen Namen und ein Gehalt.
- Ein Interface, wenn die Klassen verschieden sind, aber eine gemeinsame Fähigkeit brauchen: Ein Mitarbeiter und eine Rechnung „können bezahlt werden“, ein Vogel und ein Flugzeug „können fliegen“.
Employee ist die Basisklasse von Manager und Praktikant und implementiert gleichzeitig IPayable.Genauer, für Neugierige
- Seit C# 8 kann ein Interface eine Standardmethode haben, mit Rumpf. Die Klassen bekommen sie fertig und können auch ihre eigene schreiben. So kannst du einem Interface eine neue Methode hinzufügen, ohne die Klassen kaputt zu machen, die es schon implementieren. So eine Methode rufst du über eine Variable vom Typ des Interface auf (
IFlyable f), nicht überBird:
interface IFlyable{ void Fly();
void Glide() { Console.WriteLine("Gleitet durch die Luft"); }}
class Bird : IFlyable{ public void Fly() { Console.WriteLine("Der Vogel schlägt mit den Flügeln"); }}
class Plane : IFlyable{ public void Fly() { Console.WriteLine("Das Flugzeug hebt ab"); }}
IFlyable[] flyers = { new Bird(), new Plane() };foreach (IFlyable f in flyers){ f.Fly(); f.Glide();}Der Vogel schlägt mit den FlügelnGleitet durch die LuftDas Flugzeug hebt abGleitet durch die Luft- Ein Interface kann keine Felder haben. Properties schon, aber nur als Versprechen:
int Amount { get; }sagt „jede Klasse wird diese Property haben“.
Übung
Verträge
Löse die Aufgaben in deinem Editor.
1. Hupe. Ein Interface IHonkable mit einer Methode Honk(). SimpleCar aus dem Modul „Klassen und Objekte“ implementiert es.
Tut-tut!2. Angaben. Ein Interface IPrintable mit einer Methode PrintDetails(). Implementiere es in Person, und lass Student (eine Unterklasse von Person) es überschreiben und den Notendurchschnitt hinzufügen. Leg beide in ein Array IPrintable[].
Name: Georg, Alter: 45Name: Elena, Alter: 19Notendurchschnitt: 5.53. Tanz. Ein Interface IDanceable mit einer Methode Dance(). DanceMove (ein Name und eine Dauer) implementiert es.
Ich tanze Salsa für 30 Sekunden4. DJ. Eine Klasse Song mit einem Titel. Ein Interface IPlayable mit einer Methode Play(). DJ implementiert es und hat eine Methode PlaySong(Song song), die sich das Lied merkt und es abspielt. Gibt es noch kein Lied, sagt Play() das.
Kein Lied ausgewähltIch spiele 'Bohemian Rhapsody'5. Der Teich. Interfaces ISwimmable mit Swim() und IFlyable mit Fly(). Fish schwimmt. Duck schwimmt und fliegt.
Der Fisch schwimmt.Die Ente schwimmt.Die Ente fliegt.Weitere Aufgaben
- Rabatt. Ein Interface IDiscountable mit einer Methode, die den Preis nach dem Rabatt zurückgibt. Implementiere es in
Product(10% Rabatt) und inSubscription(25% Rabatt für Studierende). Berechne die Summe eines Warenkorbs mit einem Produkt für 50 € und einem Studentenabo für 12 €: 45 € + 9 € = 54 €. - CSV für alle. Lass auch
EmployeeIExportable implementieren, mit einer Zeile „Name;Monatsgehalt“. Leg Mitarbeiter und Rechnungen in ein Array IExportable[].
Test
Teste dich
Verwandte Lektionen
Abschnitt „Verwandte Lektionen“- Baut auf: Kapselung · Vererbung · Polymorphie · Abstrakte Klassen
- Geht weiter in: Typprüfungen · OOP-Prinzipien · Werfen und eigene Exceptions · Listen
- Aufgaben zum Thema: Vererbung und Polymorphie