1. Einführung
Abstrakte Klasse – das ist eine Klasse, die du nicht einfach so direkt mit new erstellen kannst. Sie ist dafür da, dass auf ihrer Basis andere, konkretere Klassen gebaut werden. Das ist sozusagen eine Vorlage oder ein "Bauplan", der die allgemeine Struktur und das Verhalten für abgeleitete Klassen festlegt, aber die Details der konkreten Umsetzung diesen überlässt.
Hier eine Analogie, damit es klarer wird: Stell dir vor, du hast das Konzept "Transportmittel". In der echten Welt fährt niemand auf einem abstrakten "Transportmittel" herum (außer vielleicht in der eigenen Fantasie). Manche fahren Auto, andere schippern mit dem Schiff, wieder andere fliegen mit dem Flugzeug. Die abstrakte Klasse ist dein Bauplan "Transportmittel", der beschreibt, dass jedes Transportmittel die zentrale Fähigkeit haben muss, sich fortzubewegen (Move), aber wie genau das passiert, muss in den Kindklassen definiert werden.
// Beispiel für eine abstrakte Klasse
public abstract class Transport
{
public string Model { get; set; }
// Abstrakte Methode – ohne Implementierung
public abstract void Move();
}
Wenn du versuchst, ein Objekt vom Typ Transport zu erstellen, bekommst du einen Compilerfehler! Probier's aus – der Compiler meckert sofort und sagt dir, dass man keine Instanz einer abstrakten Klasse erzeugen kann.
Wann und warum braucht man abstrakte Klassen?
- Du willst gemeinsame Eigenschaften und Verhalten verschiedener, aber ähnlicher Objekte an einem Ort bündeln.
- Du willst einen "einheitlichen Vertrag" vorgeben: zum Beispiel "alle Nachfolger müssen sich bewegen können".
- Es gibt eine gemeinsame Implementierung einiger Methoden, aber ein Teil der Logik muss in den abgeleiteten Klassen konkretisiert werden.
Eine abstrakte Klasse ist ein super Tool fürs Design der Architektur eines großen Systems, wo es gemeinsame "Features" für alle gibt, aber die Details jeder Nachfolger selbst umsetzen muss.
2. Unterschied zwischen abstrakter und normaler Klasse
Hier gibt's oft Fragen, also lass uns das mal ganz klar machen!
Abstrakte Klasse vs Normale Klasse
- Normale Klasse kannst du mit new erstellen, sie kann alle Methoden schon implementiert haben.
- Abstrakte Klasse:
- kannst du nicht direkt instanziieren (abstract im Klassendeklaration)
- kann sowohl implementierte als auch abstrakte (ohne Body) Methoden enthalten
- kann Felder, Properties, Konstruktoren und sogar statische Methoden haben
public abstract class Animal
{
public string Name { get; set; }
public void Sleep()
{
Console.WriteLine("Das Tier schläft!");
}
public abstract void MakeSound();
}
3. Abstrakte Methoden und Properties
Abstrakte Methode
Eine abstrakte Methode wird in einer abstrakten Klasse deklariert, hat aber keinen Body (keine Implementierung). Die abgeleitete Klasse muss sie so implementieren, wie sie es braucht.
public abstract class Animal
{
public string Name { get; set; }
// Abstrakte Methode
public abstract void MakeSound();
}
Abstraktes Property
Du kannst auch ein abstraktes Property deklarieren, damit alle Nachfolger es implementieren müssen:
public abstract class Shape
{
// Abstraktes Property
public abstract double Area { get; }
}
4. Vererbung von abstrakten Klassen
Eine abstrakte Klasse kann sowohl abstrakte als auch implementierte Methoden haben. Die abgeleitete Klasse muss alle abstrakten Methoden und Properties implementieren, sonst wird sie selbst abstrakt.
Lass uns unsere "Tier-App" weiterentwickeln, die wir in den vorherigen Vorlesungen angefangen haben:
public abstract class Animal
{
public string Name { get; set; }
public void Sleep()
{
Console.WriteLine($"{Name} schläft süß!");
}
public abstract void MakeSound();
}
Jetzt erstellen wir eine normale, nicht-abstrakte Klasse:
public class Dog : Animal
{
public override void MakeSound()
{
Console.WriteLine($"{Name} sagt: Wuff!");
}
}
Probier mal, MakeSound() nicht zu implementieren – und du siehst einen Compilerfehler.
Diagramm: Klassenhierarchie mit abstrakter Klasse
+---------------------+
| Animal (abstrakt) |
+---------------------+
| Name: string |
| + Sleep() |
| + MakeSound()* |
+---------------------+
^
|
+------+-------+
| |
Dog Cat
* MakeSound() – abstrakte Methode
5. Polymorphismus in Aktion: Wie Abstraktion in der Praxis funktioniert
Lass uns das Ganze mal in einer Mini-App anwenden. Stell dir vor, wir haben eine Liste von Tieren – sagen wir Hunde und Katzen.
List<Animal> animals = new List<Animal>
{
new Dog { Name = "Rex" },
new Cat { Name = "Whiskers" }
};
foreach (var animal in animals)
{
animal.MakeSound(); // Polymorpher Aufruf: es läuft die eigene Methode
animal.Sleep(); // Wird die im Basisklasse implementierte Methode aufgerufen
}
Die Klasse Cat machen wir genauso:
public class Cat : Animal
{
public override void MakeSound()
{
Console.WriteLine($"{Name} sagt: Miau!");
}
}
Praktischer Nutzen in echten Projekten
Abstrakte Klassen werden in der Architektur großer Anwendungen oft genutzt. Zum Beispiel gibt es in Grafikeditoren meist eine abstrakte Klasse Shape mit Methoden wie Draw() und Properties wie Area. Konkrete Figuren (Rechteck, Kreis) implementieren diese abstrakten Methoden mit ihren eigenen Formeln. So kannst du viele verschiedene Figuren bauen, ohne die ganze Logik neu zu schreiben.
6. Besonderheiten und Einschränkungen abstrakter Klassen
Instanzieren geht nicht
Der Compiler verbietet dir strikt sowas:
Animal a = new Animal(); // Fehler!
Nur abgeleitete (konkrete) Klassen kannst du instanziieren.
Abstrakte Klassen können enthalten:
- Deklarationen abstrakter Methoden (ohne Implementierung)
- Implementierte Methoden (mit Body)
- Properties und Felder (abstrakt oder implementiert)
- Konstruktoren – ja, eine abstrakte Klasse kann Konstruktoren haben, aber die werden nur von Nachfolgern aufgerufen, nicht direkt vom Code, der ein Objekt erstellt.
Abstrakte Klassen unterstützen Vererbung
Du kannst ganze Abstraktionshierarchien bauen:
public abstract class Creature
{
public abstract void Live();
}
public abstract class Animal : Creature
{
public abstract override void Live();
public abstract void MakeSound();
}
Wenn in der Vererbungskette eine Klasse nicht alle abstrakten Methoden implementiert, muss sie selbst als abstrakt deklariert werden!
7. Abstrakte Klassen und Konstruktoren
Eine abstrakte Klasse kann Konstruktoren enthalten, die aus den Konstruktoren der abgeleiteten Klassen aufgerufen werden.
Wichtiges Detail: Konstruktoren abstrakter Klassen werden meistens als protected deklariert, um zu zeigen, dass sie nur für Nachfolger gedacht sind und nicht für die direkte Instanziierung.
public abstract class Animal
{
public string Name { get; set; }
// Konstruktor als protected deklariert
protected Animal(string name)
{
Name = name;
Console.WriteLine($"Tier mit dem Namen {name} wurde erstellt");
}
public abstract void MakeSound();
}
public class Dog : Animal
{
public Dog(string name) : base(name)
{
// base(name) ruft den Animal-Konstruktor auf
Console.WriteLine("Hund wurde erstellt");
}
public override void MakeSound()
{
Console.WriteLine($"{Name} sagt: Wuff!");
}
}
Warum protected-Konstruktoren?
- Klarheit der Absicht: protected zeigt klar, dass der Konstruktor nur für Nachfolger gedacht ist
- Fehler vermeiden: Auch wenn der Compiler die Instanziierung mit new verbietet, macht ein protected Konstruktor das auch im API der Klasse klar und verhindert Missverständnisse
- Gute Praxis: Prinzipien wie Kapselung und ein klares API einhalten
Du kannst auch public Konstruktoren in abstrakten Klassen nutzen, aber protected passt besser zum Zweck.
8. Vergleich: abstrakte vs virtuelle Methoden
Abstrakte Methoden sind eine Art von virtuellen Methoden (sie sind im Prinzip immer virtual, auch wenn das Wort nicht benutzt wird), aber sie dürfen keine Implementierung haben. Wenn du willst, dass eine Methode von Nachfolgern überschrieben werden kann (aber nicht muss), nimm virtual. Wenn sie überschrieben werden muss, dann abstract.
public abstract class Worker
{
// Jeder muss das implementieren
public abstract void Work();
// Jeder kann überschreiben, muss aber nicht
public virtual void Rest()
{
Console.WriteLine("Standard-Pause!");
}
}
| Abstrakte Methode | Virtuelle Methode | |
|---|---|---|
| Implementierung im Basisklasse vorhanden | Nein, nur Signatur (Deklaration ohne Body) | Ja, Standard-Implementierung vorhanden |
| Wo kann man deklarieren | Nur in abstrakten Klassen | In jeder nicht-sealed Klasse, auch in abstrakten |
| Muss der Nachfolger überschreiben? | Pflicht für den ersten nicht-abstrakten Nachfolger | Nein. Kann überschrieben werden, muss aber nicht |
| Direkt aufrufbar? | Nein (kein Body vorhanden) | Ja (es läuft die Basisklassen-Version) |
| Hauptzweck | Nachfolger zwingen, eine eigene Implementierung zu liefern. Einen "Vertrag" festlegen | Nachfolgern die Möglichkeit geben, das Verhalten zu ändern, falls nötig |
9. Anwendung abstrakter Klassen in deinem Projekt
Wir entwickeln unsere Lern-App weiter. Jetzt brauchen wir eine abstrakte Klasse zur Verarbeitung von Benutzereingaben (zum Beispiel verschiedene Formulare).
public abstract class InputForm
{
public string Title { get; set; }
// Abstrakte Methode, die den Eingabeprozess umsetzt
public abstract void ShowAndHandleInput();
}
public class LoginForm : InputForm
{
public override void ShowAndHandleInput()
{
Console.WriteLine($"=== {Title} ===");
Console.Write("Login eingeben: ");
string login = Console.ReadLine();
Console.Write("Passwort eingeben: ");
string password = Console.ReadLine();
Console.WriteLine("Authentifizierung abgeschlossen!");
}
}
public class RegistrationForm : InputForm
{
public override void ShowAndHandleInput()
{
Console.WriteLine($"=== {Title} ===");
Console.Write("Login ausdenken: ");
string login = Console.ReadLine();
Console.Write("Passwort ausdenken: ");
string password = Console.ReadLine();
Console.WriteLine("Registrierung abgeschlossen!");
}
}
Verwendung:
InputForm[] forms = new InputForm[]
{
new LoginForm { Title = "Anmeldung" },
new RegistrationForm { Title = "Registrierung" }
};
foreach (var form in forms)
{
form.ShowAndHandleInput();
}
Dieser Ansatz wird immer mächtiger, je größer dein Programm wird. Die abstrakte Klasse ist eine super Basis für erweiterbare Systeme, wo DRY und SOLID (Don't Repeat Yourself, modular und erweiterbar schreiben) deine besten Freunde sind.
10. Typische Fehler beim Arbeiten mit abstrakten Klassen und Methoden
Fehler Nr. 1: Abstrakte Methode vergessen zu implementieren.
Wenn die abgeleitete Klasse nicht alle abstrakten Methoden und Properties implementiert, gibt der Compiler einen Fehler aus. Das passiert oft, besonders bei fremdem Code oder großen Hierarchien. Check nochmal genau, ob du nichts Wichtiges übersehen hast.
Fehler Nr. 2: Versuch, eine Methode gleichzeitig abstract und static zu machen.
Das geht nicht. Abstrakte Methoden sind für die Implementierung in Instanzen gedacht, statische brauchen gar keine Instanz. Die beiden Ansätze widersprechen sich, und der Compiler lässt dich das nicht kompilieren.
Fehler Nr. 3: "Faules" Vermeiden der Implementierung aller abstrakten Members.
Wenn du nicht alle abstrakten Methoden implementieren willst, musst du die Klasse auch als abstract deklarieren. Das ist kein Fehler an sich, kann aber ein Zeichen für eine verworrene Architektur sein. Manchmal lohnt es sich, eine Teilimplementierung in eine Zwischen-abstrakte Klasse auszulagern, um es den Nachfolgern leichter zu machen.
GO TO FULL VERSION