Interfaces und Abstrakte Klassen in C#
Lernen Sie Interfaces und abstrakte Klassen in C#, ihre Unterschiede und den Einsatz für sauberes, erweiterbares Design.
In C# werden abstrakte Klassen und Interfaces verwendet, um flexible und erweiterbare objektorientierte Architekturen aufzubauen.
Beide helfen dabei, gemeinsames Verhalten zu definieren, aber es gibt wichtige Unterschiede.
Eine abstract class kann eine teilweise Implementierung (Methodenkörper) bereitstellen, während ein interface nur
Signaturen deklariert und die Implementierung vollständig den abgeleiteten Klassen überlässt.
Abstrakte Klasse
Eine abstrakte Klasse wird mit dem Schlüsselwort abstract definiert und kann nicht direkt instanziiert werden.
Sie definiert gemeinsame Eigenschaften und Methoden, aber einige Methoden können als abstract markiert werden, sodass Unterklassen sie implementieren müssen.
Abstrakte Klassen können auch normale Methoden enthalten – also teilweise implementiertes Verhalten.
public abstract class Shape
{
public string Color { get; set; } = "Schwarz";
// Muss von Unterklassen implementiert werden
public abstract double GetArea();
// Eine gemeinsame Methode
public void PrintColor()
{
Console.WriteLine($"Farbe: {Color}");
}
}
public class Circle : Shape
{
public double Radius { get; set; }
public override double GetArea()
{
return Math.PI * Radius * Radius;
}
}
Hier stellt die abstrakte Klasse Shape eine gemeinsame Color-Eigenschaft bereit,
überlässt aber die Implementierung von GetArea() den Unterklassen.
Die Klasse Circle implementiert dies auf ihre eigene Weise.
Interface
Ein Interface wird mit dem Schlüsselwort interface definiert.
Es enthält nur Methodensignaturen und Property-Signaturen, jedoch keine Implementierungen (vor C# 8).
Eine Klasse kann mehrere Interfaces gleichzeitig implementieren (eine Form von Mehrfachvererbung).
Dadurch wird ein „gemeinsamer Vertrag“ zwischen verschiedenen Klassen geschaffen.
public interface ILogger
{
void Log(string message);
}
public class FileLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine($"In Datei protokolliert: {message}");
}
}
public class DbLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine($"In Datenbank protokolliert: {message}");
}
}
Klassen, die ILogger implementieren, führen das Logging auf unterschiedliche Weise aus,
müssen jedoch alle dieselbe Methodensignatur bereitstellen: Log(string).
Abstrakte Klasse vs. Interface
Die wichtigsten Unterschiede:
- Abstrakte Klasse: Kann gemeinsames Verhalten und verpflichtendes Verhalten definieren. Kann Eigenschaften und Methodenkörper enthalten.
- Interface: Deklariert nur Signaturen. Unterstützt Mehrfachimplementierung.
- Abstrakte Klasse: Eine Klasse kann nur von einer abstrakten (Basisklasse) erben.
- Interface: Eine Klasse kann mehrere Interfaces implementieren.
- Abstrakte Klasse: Kann gemeinsamen Zustand, Felder und einen Konstruktor enthalten.
- Interface: Enthält keine Felder oder Konstruktoren.
Praxisbeispiel: Produktionslinie (Interface + Abstrakte Klasse)
Im folgenden Beispiel definieren wir ein IProduct-Interface, um einen Produktfertigungsablauf zu modellieren, sowie eine
abstract class (ProductBase), die diesen teilweise implementiert.
IProduct deklariert den gemeinsamen Vertrag (Eigenschaften + Methodensignaturen), während ProductBase
den gemeinsamen Ablauf (Template Method: Production()) und eine Teilimplementierung bereitstellt.
MetalProduct, WoodProduct und PlasticProduct passen diese Struktur an.
Wir verwenden ein Enum (SizeOption) für konsistente Größenwerte wie „S/M/L“
und ein einfaches Qualitätsprüfungs-Interface IQualityCheck, das die Produkte implementieren.
using System;
using System.Collections.Generic;
// Enum zur Standardisierung von Größen
public enum SizeOption
{
Small,
Medium,
Large
}
// Gemeinsamer Vertrag: Eigenschaften und Verhaltensweisen, die alle Produkte haben müssen
public interface IProduct
{
string Name { get; set; }
string SKU { get; set; }
SizeOption Size { get; set; }
decimal UnitCost { get; set; } // Stückkosten
int Stock { get; set; } // Lagerbestand
// Produktionsablauf und Kostenberechnung
void Production();
decimal CalculateCost(int quantity);
string GetSpecs();
}
// Einfacher Vertrag für Qualitätsprüfung
public interface IQualityCheck
{
bool QualityCheck(out string reason);
}
// ABSTRAKTE KLASSE: Teilimplementierung + gemeinsamer Ablauf (Template Method Pattern)
public abstract class ProductBase : IProduct, IQualityCheck
{
public string Name { get; set; } = string.Empty;
public string SKU { get; set; } = string.Empty;
public SizeOption Size { get; set; }
public decimal UnitCost { get; set; }
public int Stock { get; set; }
// Materialabhängiger Faktor (Unterklassen können überschreiben)
protected virtual decimal MaterialFactor => 1.00m;
// Template Method: führt Produktionsschritte in festgelegter Reihenfolge aus
public void Production()
{
LogStep($"Produktion gestartet: {Name} ({SKU})");
PrepareMaterials();
Assemble();
Finish();
LogStep("Produktion abgeschlossen.\n");
}
// Basiskostenberechnung (Unterklassen können überschreiben)
public virtual decimal CalculateCost(int quantity)
{
if (quantity <= 0) return 0m;
// Einfaches Modell: Stückkosten * Menge * Materialfaktor * Gemeinkosten (10%)
decimal baseCost = UnitCost * quantity * MaterialFactor;
decimal overhead = baseCost * 0.10m;
return baseCost + overhead;
}
// Gemeinsame technische Spezifikation
public virtual string GetSpecs()
=> $"SKU: {SKU}, Größe: {Size}, Stückkosten: {UnitCost:0.00} EUR, Lager: {Stock}";
// Standard-Qualitätsprüfung: Sind Kosten und Bestand plausibel?
public virtual bool QualityCheck(out string reason)
{
if (UnitCost <= 0) { reason = "Stückkosten dürfen nicht null/negativ sein."; return false; }
if (Stock < 0) { reason = "Lagerbestand darf nicht negativ sein."; return false; }
reason = "OK";
return true;
}
// Schritte, die Unterklassen implementieren müssen
protected abstract void PrepareMaterials();
protected abstract void Assemble();
protected virtual void Finish()
{
LogStep("Allgemeine Qualitätskontrolle und Verpackung");
}
protected void LogStep(string message) => Console.WriteLine($" - {message}");
}
// METALLPRODUKT
public class MetalProduct : ProductBase
{
public string Alloy { get; set; } = "Stahl 304L";
public bool RequiresHeatTreatment { get; set; } = true;
protected override decimal MaterialFactor => 1.45m; // Metallbearbeitung ist teurer
protected override void PrepareMaterials()
{
LogStep($"Metallblech/Block wird vorbereitet (Legierung: {Alloy})");
if (RequiresHeatTreatment) LogStep("Ofen für Wärmebehandlung vorbereiten");
}
protected override void Assemble()
{
LogStep("Schneiden, Biegen, Schweißen und Oberflächenschleifen");
if (RequiresHeatTreatment) LogStep("Wärmebehandlung wird durchgeführt (Härtung)");
}
protected override void Finish()
{
LogStep("Korrosionsschutz und Oberflächenbeschichtung");
base.Finish();
}
public override string GetSpecs()
=> $"[Metall] {Name} | {base.GetSpecs()} | Legierung: {Alloy} | Wärmebehandlung: {(RequiresHeatTreatment ? "Ja" : "Nein")}";
public override bool QualityCheck(out string reason)
{
if (!base.QualityCheck(out reason)) return false;
// Zusatzregel für Metall: Legierungsname darf nicht leer sein
if (string.IsNullOrWhiteSpace(Alloy)) { reason = "Legierungsangabe darf nicht leer sein."; return false; }
return true;
}
}
// HOLZPRODUKT
public class WoodProduct : ProductBase
{
public string WoodType { get; set; } = "Eiche";
public bool IsVarnished { get; set; } = true;
protected override decimal MaterialFactor => 1.20m;
protected override void PrepareMaterials()
{
LogStep($"Auswahl und Trocknung des Holzes (Art: {WoodType})");
LogStep("Zuschnitt und Schleifen");
}
protected override void Assemble()
{
LogStep("Verbindung: Zapfen/Dübel und Leim");
}
protected override void Finish()
{
LogStep(IsVarnished ? "Lackieren" : "Öl/Schutzmittel auftragen");
base.Finish();
}
public override string GetSpecs()
=> $"[Holz] {Name} | {base.GetSpecs()} | Holzart: {WoodType} | Lackiert: {(IsVarnished ? "Ja" : "Nein")}";
}
// PLASTIKPRODUKT
public class PlasticProduct : ProductBase
{
public string Polymer { get; set; } = "PP";
public bool Recyclable { get; set; } = true;
protected override decimal MaterialFactor => 0.95m; // Kunststoffe sind meist günstiger
protected override void PrepareMaterials()
{
LogStep($"Granulat wird vorbereitet (Polymer: {Polymer})");
LogStep("Farb-Masterbatch wird beigemischt");
}
protected override void Assemble()
{
LogStep("Spritzgießen und Entgraten");
}
protected override void Finish()
{
LogStep("Oberflächenkontrolle und Etikettierung");
base.Finish();
}
public override string GetSpecs()
=> $"[Plastik] {Name} | {base.GetSpecs()} | Polymer: {Polymer} | Recycelbar: {(Recyclable ? "Ja" : "Nein")}";
}
// DEMO
class Program
{
static void Main()
{
var items = new List<IProduct>
{
new MetalProduct
{
Name = "Stahlregal",
SKU = "M-SH-001",
Size = SizeOption.Large,
UnitCost = 500m,
Stock = 40,
Alloy = "304L",
RequiresHeatTreatment = true
},
new WoodProduct
{
Name = "Holztisch",
SKU = "W-TB-002",
Size = SizeOption.Medium,
UnitCost = 350m,
Stock = 12,
WoodType = "Eiche",
IsVarnished = true
},
new PlasticProduct
{
Name = "Plastikkiste",
SKU = "P-CR-003",
Size = SizeOption.Small,
UnitCost = 80m,
Stock = 200,
Polymer = "PP",
Recyclable = true
}
};
const int qty = 10;
foreach (var p in items)
{
Console.WriteLine($"\n=== {p.Name} ===");
Console.WriteLine(p.GetSpecs());
p.Production();
Console.WriteLine($"Gesamtkosten ({qty} Stück): {p.CalculateCost(qty):0.00} EUR");
if (p is IQualityCheck qc)
{
Console.WriteLine(qc.QualityCheck(out var reason)
? "Qualitätsprüfung: OK"
: $"Qualitätsprüfung: FEHLER - {reason}");
}
}
}
}
Zusammenfassung: Das IProduct-Interface stellt einen gemeinsamen Vertrag für verschiedene Produkttypen bereit.
Die abstrakte Klasse ProductBase implementiert diesen Vertrag teilweise und standardisiert den Produktionsablauf (Template Method).
Die Unterklassen (Metall/Holz/Plastik) passen materialspezifische Schritte und Kostenannahmen an.
Durch die Kombination von Interface + abstrakter Klasse erhält man sowohl Flexibilität als auch Wiederverwendbarkeit.
Vorteile
- Interface: Flexibilität dank Mehrfachimplementierung.
- Abstrakte Klasse: Reduziert Code-Duplizierung durch gemeinsamen Zustand und Teilimplementierung.
- Beide: Verringern Abhängigkeiten und erhöhen die Testbarkeit.
TL;DR
abstract class: Definiert gemeinsames + verpflichtendes Verhalten; kann Methodenkörper enthalten.interface: Deklariert nur Signaturen; unterstützt Mehrfachimplementierung.- Eine Klasse kann nur von einer abstrakten/Basisklasse erben, aber mehrere Interfaces implementieren.
Ähnliche Artikel
Delegates und Ereignisse in C#
Lernen Sie Delegates und Events in C#, um ereignisgesteuerte Programmierung mit Callbacks und Beispielen umzusetzen.
Dependency Injection Grundlagen in C#
Lernen Sie die Grundlagen von Dependency Injection in C#, um Abhängigkeiten zu verwalten und lose Kopplung zu erreichen.
Erweiterungsmethoden in C#
Lernen Sie Erweiterungsmethoden in C#, um bestehenden Typen neue Funktionen hinzuzufügen, ohne den Code zu ändern.
Kapselung, Vererbung und Polymorphismus in C#
Lernen Sie Kapselung, Vererbung und Polymorphismus in C# mit Beispielen, um zentrale OOP-Konzepte sicher anzuwenden.
Klassen, Objekte, Eigenschaften und Methoden in C#
Erlernen Sie die Grundlagen von Klassen, Objekten, Eigenschaften und Methoden in C# für objektorientierte Programmierung.
Namespaces und Assemblies in C#
Erlernen Sie die Konzepte von Namespaces und Assemblies in C#, um Code zu strukturieren und Abhängigkeiten korrekt zu verwalten.
Sealed-, Static- und Partial-Klassen in C#
Lernen Sie Zweck, Unterschiede und Einsatzszenarien von sealed-, static- und partial-Klassen in C#.
SOLID-Prinzipien mit C#
SOLID-Prinzipien mit C#-Beispielen: flexiblen, wartbaren und testbaren Code erstellen.