Komposition statt Vererbung
Teil des Abschnitts Objektorientierte Programmierung der C#-Journey von Coddy — Lektion 41 von 70.
Vererbung erzeugt eine enge Kopplung zwischen Klassen. Wenn eine Car von Vehicle erbt, kann jede Änderung an Vehicle die Klasse Car beeinträchtigen. Komposition bietet eine flexiblere Alternative: Anstatt Verhalten zu erben, enthält eine Klasse Objekte, die dieses Verhalten bereitstellen.
Stellen Sie sich einen Spielcharakter vor, der sich bewegen und angreifen kann. Mit Vererbung könnten Sie eine komplexe Hierarchie erstellen. Mit Komposition geben Sie dem Charakter einfach die Komponenten, die er benötigt:
public class MovementBehavior
{
public void Move() => Console.WriteLine("Moving...");
}
public class AttackBehavior
{
public void Attack() => Console.WriteLine("Attacking!");
}
public class Character
{
private MovementBehavior movement = new MovementBehavior();
private AttackBehavior attack = new AttackBehavior();
public void Move() => movement.Move();
public void Attack() => attack.Attack();
}Der entscheidende Unterschied ist die Beziehung. Vererbung repräsentiert "ist-ein" (ein Hund ist ein Tier), während Komposition "hat-ein" repräsentiert (ein Charakter hat ein Bewegungsverhalten). Komposition ermöglicht es Ihnen, Verhaltensweisen zur Laufzeit auszutauschen, Fähigkeiten frei zu kombinieren und das Problem der fragilen Basisklasse zu vermeiden.
Verwenden Sie Vererbung, wenn eine echte hierarchische Beziehung besteht. Wählen Sie Komposition, wenn Sie Flexibilität benötigen, wenn Verhaltensweisen über nicht verwandte Klassen hinweg geteilt werden könnten oder wenn Sie das Verhalten dynamisch ändern möchten.
Aufgabe
EinfachLassen Sie uns ein Benachrichtigungssystem erstellen, das die Vorteile von Komposition gegenüber Vererbung demonstriert. Anstatt eine komplexe Hierarchie von Benachrichtigungsklassen zu erstellen, werden Sie einen NotificationSender zusammensetzen, indem Sie ihm austauschbare Verhaltenskomponenten geben.
Sie werden Ihren Code über drei Dateien organisieren:
Behaviors.cs: Erstellen Sie zwei Verhaltensklassen im NamespaceNotifications, die verschiedene Aspekte des Sendens von Benachrichtigungen behandeln:FormattingBehavior- verfügt über eine MethodeFormat(string message), die die Nachricht zurückgibt, umschlossen von[ALERT]am Anfang und[END]am EndeDeliveryBehavior- verfügt über eine MethodeDeliver(string channel, string message), die einen String im FormatSending via {channel}: {message}zurückgibt
NotificationSender.cs: Erstellen Sie eine KlasseNotificationSenderim NamespaceNotifications, die Komposition verwendet, um Verhaltensweisen zu kombinieren. Ihr Sender sollte:- Instanzen von sowohl
FormattingBehaviorals auchDeliveryBehaviorals private Felder enthalten - Über eine Eigenschaft
Channel(string) verfügen, die den Namen des Übertragungskanals speichert - Einen Konstruktor haben, der den Kanalnamen entgegennimmt
- Eine Methode
Send(string message)haben, die zuerst die Nachricht mit dem Formatierungsverhalten formatiert, sie dann mit dem Zustellungsverhalten unter Verwendung des Kanals zustellt und das Endergebnis zurückgibt
- Instanzen von sowohl
Program.cs: Erstellen Sie in Ihrer Hauptdatei einenNotificationSendermit einem Kanal aus der Eingabe und senden Sie dann eine Nachricht aus der Eingabe. Geben Sie das Ergebnis aus, um zu zeigen, wie die zusammengesetzten Verhaltensweisen zusammenarbeiten.
Sie erhalten zwei Eingaben:
- Den Namen des Übertragungskanals (z. B.
Email,SMS) - Die zu sendende Nachricht
Geben Sie das Ergebnis des Aufrufs von Send() auf Ihrem Benachrichtigungssender aus.
Wenn die Eingaben beispielsweise Email und Server is down sind, sollte die Ausgabe wie folgt aussehen:
Sending via Email: [ALERT] Server is down [END]Beachten Sie, dass der NotificationSender von nichts erbt – er "hat" einfach Formatierungs- und Zustellungsverhaltensweisen. Dieser Kompositionsansatz bedeutet, dass Sie jedes Verhalten problemlos austauschen könnten, ohne die Sender-Klasse zu ändern, oder diese Verhaltensweisen in völlig anderen Klassen wiederverwenden könnten!
Spickzettel
Komposition ist eine Alternative zur Vererbung, die flexibleren Code erstellt, indem eine Klasse Objekte enthält, die Verhalten bereitstellen, anstatt Verhalten von einer Basisklasse zu erben.
Vererbung vs. Komposition:
- Vererbung repräsentiert „ist-ein“-Beziehungen (ein Hund ist ein Tier)
- Komposition repräsentiert „hat-ein“-Beziehungen (ein Charakter hat ein Bewegungsverhalten)
Vorteile der Komposition:
- Verhalten zur Laufzeit austauschen
- Fähigkeiten frei kombinieren
- Enge Kopplung und Probleme mit fragilen Basisklassen vermeiden
- Verhalten über nicht verwandte Klassen hinweg teilen
Beispiel mit Verhaltenskomponenten:
public class MovementBehavior
{
public void Move() => Console.WriteLine("Moving...");
}
public class AttackBehavior
{
public void Attack() => Console.WriteLine("Attacking!");
}
public class Character
{
private MovementBehavior movement = new MovementBehavior();
private AttackBehavior attack = new AttackBehavior();
public void Move() => movement.Move();
public void Attack() => attack.Attack();
}Wann welcher Ansatz verwendet werden sollte:
- Verwenden Sie Vererbung für echte hierarchische Beziehungen
- Wählen Sie Komposition für Flexibilität, geteilte Verhaltensweisen oder dynamische Verhaltensänderungen
Probier es selbst
using System;
using Notifications;
class Program
{
public static void Main(string[] args)
{
// Eingabe lesen
string channel = Console.ReadLine();
string message = Console.ReadLine();
// TODO: Erstelle einen NotificationSender mit dem channel
// TODO: Sende die Nachricht und gib das Ergebnis aus
}
}
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Objektorientierte Programmierung
1Grundlagen der OOP
Externe DateienNamespaces & DirektivenEinführung in Klassen & ObjekteDas 'this'-SchlüsselwortMethoden und ParameterFelder vs. EigenschaftenKonstruktorenObjekt-InitialisiererZusammenfassung - Einfacher Taschenrechner4Vererbung
Grundlagen der Vererbung (:) SyntaxDas 'base'-SchlüsselwortVirtual & Override SchlüsselwörterVersiegelte KlassenDie 'object'-BasisklasseZusammenfassung – Mitarbeiter-Hierarchie7Fortgeschrittene Funktionen
OperatorüberladungIndexer (this[])ToString() überschreibenErweiterungsmethodenZusammenfassung - Benutzerdefinierte Liste2Eigenschaften & Statische Member
Automatisch implementierte EigenschaftenRead-Only- & Write-Only-EigenschaftenStatische Felder & MethodenStatische KlassenExpression-Bodied Member5Polymorphismus & Interfaces
Compile- vs. Runtime-PolymorphismusInterface vs. abstrakte KlasseMehrfache InterfacesExplizite InterfacesUpcasting & DowncastingRecap – Shape Calculator8Fortgeschrittene OOP-Konzepte
Komposition statt VererbungGenerics (Klassen & Methoden)Delegates und EventsAttribute und ReflectionIDisposable & using-AnweisungDependency Injection Grundlagen3Klassenarchitektur
Instanz- vs. statische Daten'readonly' & 'const' SchlüsselwörterBacking FieldsZusammenfassung - Bankkonto-Manager6Kapselung
ZugriffsmodifikatorenProperties zur KapselungImplementierung von Data HidingImmutability-MusterRückblick – Studierendendaten