Umgestaltung mithilfe von Schnittstellen
In der vorherigen Lektion haben Sie die Herausforderungen von eng gekoppelten Code kennengelernt und wie es gegen das Open/Closed-Prinzip verstößt. Jetzt wollen wir das Library und BorrowableBook Beispiel umgestalten, damit das System flexibler und einfacher zu warten ist.
Hier ist der ursprüngliche Code, der das Problem veranschaulicht:
public class BorrowableBook
{
public string Title { get; set; }
public bool IsAvailable { get; private set; } = true;
public BorrowableBook(string title)
{
Title = title;
}
public void Borrow()
{
if (IsAvailable)
{
IsAvailable = false;
Console.WriteLine($"You have borrowed \"{Title}\".");
}
else
{
Console.WriteLine($"\"{Title}\" is already borrowed.");
}
}
}
public class Library
{
private BorrowableBook _book;
public Library(BorrowableBook book)
{
_book = book;
}
public void BorrowBook()
{
if (_book.IsAvailable)
{
_book.Borrow();
}
else
{
Console.WriteLine("The book is not available.");
}
}
}
Dieses Design koppelt die Library Klasse eng mit der BorrowableBook Klasse, wodurch es schwierig wird, neue Arten von leihbaren Elementen (z. B. DVDs) hinzuzufügen, ohne die Library Klasse zu ändern. Um die enge Kopplung zu beheben, stellen wir eine Schnittstelle zum Entkoppeln des Kreditverhaltens vor.
Einführung von Schnittstellen
Eine Schnittstelle legt einen Vertrag für das Verhalten fest, ohne die Implementierung detailliert zu erläutern. Die Einführung einer Schnittstelle abstrahiert die Kreditfunktion und erhöht die Flexibilität des Systems.
Dies ist die IBorrowable Schnittstelle:
public interface IBorrowable
{
bool IsAvailable { get; }
void Borrow();
}
Diese Schnittstelle führt zwei Mitglieder ein.
-
IsAvailable: Eine Eigenschaft, die angibt, ob das Element für die Kreditaufnahme verfügbar ist. -
Borrow: Eine Methode zum Ausleihen des Gegenstands.
Durch die Definition dieser Schnittstelle entkoppeln wir das Kreditverhalten von einer bestimmten Implementierung.
Umgestaltung der BorrowableBook-Klasse
Als Nächstes aktualisieren wir die BorrowableBook Klasse, um die IBorrowable Schnittstelle zu implementieren:
public class BorrowableBook : IBorrowable
{
public string Title { get; set; }
public bool IsAvailable { get; private set; } = true;
public BorrowableBook(string title)
{
Title = title;
}
public void Borrow()
{
if (IsAvailable)
{
IsAvailable = false;
Console.WriteLine($"You have borrowed \"{Title}\".");
}
else
{
Console.WriteLine($"\"{Title}\" is already borrowed.");
}
}
}
Nun entspricht die BorrowableBook Klasse der IBorrowable Schnittstelle und macht sie austauschbar mit anderen Klassen, die dieselbe Schnittstelle implementieren.
Umgestalten der Library-Klasse
Außerdem aktualisieren wir die Library Klasse so, dass sie von der IBorrowable Schnittstelle anstelle der konkreten BorrowableBook Klasse abhängig ist:
public class Library
{
private IBorrowable _item;
public Library(IBorrowable item)
{
_item = item;
}
public void BorrowItem()
{
if (_item.IsAvailable)
{
_item.Borrow();
}
else
{
Console.WriteLine("The item is not available.");
}
}
}
Jetzt kann die Library Klasse mit jedem Objekt arbeiten, das die IBorrowable Schnittstelle implementiert, wodurch sie flexibler und einfacher erweitert werden kann.
Verwenden der Abhängigkeitsinjektion
Stellen Sie sich vor, Sie richten ein Heimunterhaltungssystem ein. Anstatt eine bestimmte Marke von Lautsprechern dauerhaft an Ihr Stereo anzufügen, verwenden Sie Lautsprecher "Jacks", die mehrere Arten kompatibler Lautsprecherstecker akzeptieren können. Mit diesem Design können Sie die Lautsprecher problemlos ersetzen oder aktualisieren, ohne das gesamte Stereosystem ändern zu müssen.
In der Software funktioniert Dependency Injection ähnlich. Sie ermöglicht es einer Klasse, von einer abstrakten Schnittstelle und nicht von einer bestimmten Implementierung abhängig zu sein. Dependency Injection erleichtert die Verwaltung des Systems, da Sie verschiedene Implementierungen "anschließen" können, ohne die Klasse selbst zu ändern.
Der Konstruktor in der Programmierung ist wie der Techniker, der die Lautsprecherbuchsen während der Einrichtung Ihres Stereosystems verbindet. Für die Library Klasse ist der Konstruktor (public Library(IBorrowable item)) der Ort, an dem die Abhängigkeit bereitgestellt wird. Der Konstruktor ermöglicht es der Library Klasse, mit jeder kompatiblen Implementierung zu arbeiten, wie BorrowableBook oder BorrowableDVD, ohne die interne Struktur ändern zu müssen. Genau wie die Lautsprecherbuchse flexibilität bei der Auswahl verschiedener Lautsprecher ermöglicht, erleichtert der Konstruktor die Flexibilität bei der Verwendung verschiedener leihbarer Elemente.
So funktioniert es im Code:
public class Library
{
private IBorrowable _item;
public Library(IBorrowable item) // Dependency is injected here
{
_item = item;
}
public void BorrowItem()
{
if (_item.IsAvailable)
{
_item.Borrow();
}
else
{
Console.WriteLine("The item is not available.");
}
}
}
In diesem Beispiel:
- Der Konstruktor ist der Ort, an dem die „Verbindung“ (die Abhängigkeit
IBorrowable) hergestellt wird, ähnlich wie der Lautsprecherstecker mit der „Buchse“ verbunden wird. - Die
IBorrowableSchnittstelle fungiert wie die "Stereobuchse", die den Standardverbindungspunkt definiert. - Die
BorrowableBookundBorrowableDVDsind wie verschiedene Arten von Stereolautsprechern mit einzigartigen Konnektoren, die mit der „Buchse“ verbunden werden.
Mithilfe von Dependency Injection kann die Klasse Library mit jeder Implementierung von IBorrowable arbeiten. Dieser Ansatz bietet Folgendes:
-
Flexibilität: Sie können ganz einfach neue Implementierungen wechseln oder hinzufügen, ohne die
LibraryKlasse zu ändern. - Testbarkeit: Sie können simulierte Implementierungen für Testzwecke einfügen.
-
Verwendbarkeit: Die
LibraryKlasse muss die Details der spezifischen Implementierung nicht kennen und erleichtert das Erweitern und Warten.
Mit diesem Design wird sichergestellt, dass die Library Klasse nicht mehr eng mit bestimmten Implementierungen gekoppelt ist, wodurch das System modularer und anpassbarer wird.
Hinzufügen neuer ausleihbarer Elemente
Mit der vorhandenen Schnittstelle können wir ganz einfach neue Arten von leihbaren Elementen hinzufügen, ohne die Library Klasse zu ändern. Hier ist beispielsweise eine BorrowableDVD Klasse:
public class BorrowableDVD : IBorrowable
{
public string Title { get; set; }
public bool IsAvailable { get; private set; } = true;
public BorrowableDVD(string title)
{
Title = title;
}
public void Borrow()
{
if (IsAvailable)
{
IsAvailable = false;
Console.WriteLine($"You have borrowed the DVD \"{Title}\".");
}
else
{
Console.WriteLine($"The DVD \"{Title}\" is already borrowed.");
}
}
}
Die BorrowableDVD Klasse implementiert dieselbe IBorrowable Schnittstelle, sodass sie nahtlos mit der Library Klasse verwendet werden kann.
Testen des Systems
Hier ist ein Programm zum Veranschaulichen des umgestalteten Systems:
using System;
class Program
{
static void Main()
{
// Create borrowable items
IBorrowable book = new BorrowableBook("Adventure Works Cycles");
IBorrowable dvd = new BorrowableDVD("Graphic Design Institute");
// Create libraries
Library bookLibrary = new Library(book);
Library dvdLibrary = new Library(dvd);
// Borrow items
bookLibrary.BorrowItem();
bookLibrary.BorrowItem(); // Try borrowing again
Console.WriteLine();
dvdLibrary.BorrowItem();
dvdLibrary.BorrowItem(); // Try borrowing again
}
}
Die Ausgabe zeigt die Flexibilität des umgestalteten Systems:
You have borrowed "Adventure Works Cycles".
"Adventure Works Cycles" is already borrowed.
You have borrowed the DVD "Graphic Design Institute".
The DVD "Graphic Design Institute" is already borrowed.
In diesem umgestalteten Beispiel wird veranschaulicht, wie Schnittstellen Abhängigkeiten reduzieren und modularität verbessern:
-
Trennung von Bedenken: Die
IBorrowableSchnittstelle isoliert das Kreditverhalten und stellt sicher, dass dieLibraryKlasse nicht von bestimmten Implementierungen abhängt. -
Verbesserte Flexibilität: Sie können neue Arten von leihbaren Elementen (z. B. DVDs) hinzufügen, ohne die
LibraryKlasse zu ändern. - Vereinfachte Wartung: Das System ist einfacher zu verstehen, zu testen und zu erweitern, da die Verantwortlichkeiten eindeutig aufgeteilt sind.