Persönlich muss ich fast nie abstrakte Klassen schreiben.
Meistens sehe ich, dass abstrakte Klassen (falsch) verwendet werden, weil der Autor der abstrakten Klasse das Muster "Vorlagenmethode" verwendet.
Das Problem mit der "Template-Methode" ist, dass sie fast immer etwas neu ist - die "abgeleitete" Klasse kennt nicht nur die "abstrakte" Methode ihrer Basisklasse, die sie implementiert, sondern auch die öffentlichen Methoden der Basisklasse , obwohl es meistens nicht nötig ist, sie anzurufen.
(Zu stark vereinfacht) Beispiel:
abstract class QuickSorter
{
public void Sort(object[] items)
{
// implementation code that somewhere along the way calls:
bool less = compare(x,y);
// ... more implementation code
}
abstract bool compare(object lhs, object rhs);
}
Hier hat der Autor dieser Klasse einen generischen Algorithmus geschrieben und beabsichtigt, dass die Benutzer ihn verwenden, indem sie ihn "spezialisieren", indem sie ihre eigenen "Hooks" bereitstellen - in diesem Fall eine "Vergleichs" -Methode.
Die beabsichtigte Verwendung ist also ungefähr so:
class NameSorter : QuickSorter
{
public bool compare(object lhs, object rhs)
{
// etc.
}
}
Das Problem dabei ist, dass Sie zwei Konzepte übermäßig miteinander verbunden haben:
- Eine Möglichkeit, zwei Elemente zu vergleichen (welches Element sollte zuerst verwendet werden)
- Eine Methode zum Sortieren von Elementen (z. B. Quicksortierung oder Zusammenführungssortierung usw.)
Im obigen Code kann der Autor der "compare" -Methode theoretisch die "Sort" -Methode der Oberklasse erneut aufrufen ... obwohl er dies in der Praxis niemals tun möchte oder muss.
Der Preis, den Sie für diese nicht benötigte Kopplung zahlen, ist, dass es schwierig ist, die Oberklasse zu ändern, und in den meisten OO-Sprachen ist es unmöglich, sie zur Laufzeit zu ändern.
Die alternative Methode besteht darin, stattdessen das Entwurfsmuster "Strategie" zu verwenden:
interface IComparator
{
bool compare(object lhs, object rhs);
}
class QuickSorter
{
private readonly IComparator comparator;
public QuickSorter(IComparator comparator)
{
this.comparator = comparator;
}
public void Sort(object[] items)
{
// usual code but call comparator.Compare();
}
}
class NameComparator : IComparator
{
bool compare(object lhs, object rhs)
{
// same code as before;
}
}
Beachten Sie jetzt: Wir haben nur Schnittstellen und konkrete Implementierungen dieser Schnittstellen. In der Praxis brauchen Sie eigentlich nichts anderes, um ein OO-Design auf hohem Niveau zu erstellen.
Um die Tatsache zu "verbergen", dass wir die "Sortierung von Namen" mithilfe einer "QuickSort" -Klasse und eines "NameComparator" implementiert haben, schreiben wir möglicherweise noch irgendwo eine Factory-Methode:
ISorter CreateNameSorter()
{
return new QuickSorter(new NameComparator());
}
Jedes Mal, wenn Sie eine abstrakte Klasse haben, können Sie dies tun ... selbst wenn eine natürliche Wiedereintrittsbeziehung zwischen der Basisklasse und der abgeleiteten Klasse besteht, lohnt es sich normalerweise, sie explizit zu machen.
Ein letzter Gedanke: Alles, was wir oben getan haben, ist, eine "NameSorting" -Funktion mit einer "QuickSort" -Funktion und einer "NameComparison" -Funktion zu "komponieren". In einer funktionalen Programmiersprache wird dieser Programmierstil noch natürlicher. mit weniger Code.