Angenommen, wir haben eine Liste von Aufgabenentitäten und einen ProjectTask
Untertyp. Aufgaben können jederzeit geschlossen werden, es sei denn ProjectTasks
, sie können nicht geschlossen werden, sobald sie den Status Gestartet haben. Die Benutzeroberfläche sollte sicherstellen, dass die Option zum Schließen eines gestarteten ProjectTask
Objekts niemals verfügbar ist. In der Domäne sind jedoch einige Sicherheitsvorkehrungen vorhanden:
public class Task
{
public Status Status { get; set; }
public virtual void Close()
{
Status = Status.Closed;
}
}
public class ProjectTask : Task
{
public override void Close()
{
if (Status == Status.Started)
throw new Exception("Cannot close a started Project Task");
base.Close();
}
}
Wenn Sie nun Close()
eine Aufgabe aufrufen , besteht die Möglichkeit, dass der Anruf fehlschlägt, wenn er ProjectTask
den Status "Gestartet" hat, während dies bei einer Basisaufgabe nicht der Fall wäre. Dies sind jedoch die geschäftlichen Anforderungen. Es sollte scheitern. Kann dies als Verstoß gegen das Liskov-Substitutionsprinzip angesehen werden ?
quelle
public Status Status { get; private set; }
:; Andernfalls kann dieClose()
Methode umgangen werden.Task
keine bizarren Inkompatibilitäten in polymorphen Code einführen, von denen nur bekanntTask
ist. LSP ist keine Laune, sondern wurde speziell eingeführt, um die Wartbarkeit in großen Systemen zu verbessern.TaskCloser
Prozess, derclosesAllTasks(tasks)
. Dieser Prozess versucht offensichtlich nicht, Ausnahmen zu erfassen. es ist schließlich nicht Bestandteil des ausdrücklichen Vertrages vonTask.Close()
. Jetzt stellst du vorProjectTask
und plötzlichTaskCloser
beginnt dein Auslösen von (möglicherweise unbehandelten) Ausnahmen. Das ist eine große Sache!Antworten:
Ja, es ist eine Verletzung des LSP. Liskov Substitutionsprinzip erfordert , dass
Ihr Beispiel unterbricht die erste Anforderung, indem es eine Voraussetzung für den Aufruf der
Close()
Methode verschärft .Sie können das Problem beheben, indem Sie die verstärkte Vorbedingung auf die oberste Ebene der Vererbungshierarchie bringen:
Indem Sie festlegen, dass ein Aufruf von
Close()
nur in dem Zustand gültig ist, in dem dieCanClose()
Rücksendung erfolgttrue
, stellen Sie sicher , dass die Vorbedingung sowohl für die,Task
als auch für dieProjectTask
, gilt.quelle
Close
die Überprüfung durchführt und ein geschütztes Element hinzugefügt wird, ist diesDoClose
eine gültige Alternative. Ich wollte jedoch dem Beispiel des OP so nahe wie möglich kommen. es zu verbessern ist eine separate Frage.Ja. Dies verletzt LSP.
Mein Vorschlag ist, der Basisaufgabe eine
CanClose
Methode / Eigenschaft hinzuzufügen , damit jede Aufgabe erkennen kann, ob die Aufgabe in diesem Zustand geschlossen werden kann. Es kann auch einen Grund dafür liefern. Und entfernen Sie die virtuelle ausClose
.Basierend auf meinem Kommentar:
quelle
Das Liskov-Substitutionsprinzip besagt, dass eine Basisklasse durch eine seiner Unterklassen ersetzt werden kann, ohne dass die gewünschten Eigenschaften des Programms geändert werden. Da
ProjectTask
beim Schließen nur eine Ausnahme ausgelöst wird, müsste ein Programm geändert werden, um dem Rechnung zu tragen,ProjectTask
das anstelle von verwendet werden sollteTask
. Es ist also eine Verletzung.Aber wenn Sie ändern
Task
in seiner Unterschrift die besagt , dass es möglicherweise eine Ausnahme auslösen , wenn sie geschlossen, dann werden Sie sich nicht gegen den Grundsatz verstoßen.quelle
Eine LSP-Verletzung erfordert drei Parteien. Der Typ T, der Subtyp S und das Programm P, das T verwendet, aber eine Instanz von S erhält.
Ihre Frage enthält T (Aufgabe) und S (Projektaufgabe), nicht jedoch P. Ihre Frage ist also unvollständig und die Antwort ist qualifiziert: Wenn ein P existiert, das keine Ausnahme erwartet, haben Sie für dieses P einen LSP Verletzung. Wenn jedes P eine Ausnahme erwartet, liegt keine LSP-Verletzung vor.
Aber Sie tun einen haben SRP Verletzung. Die Tatsache , dass der Zustand einer Aufgabe geändert werden kann, und die Politik , dass bestimmte Aufgaben in bestimmten Staaten sollen nicht auf andere Staaten, sind zwei sehr unterschiedliche Zuständigkeiten geändert werden.
Diese beiden Verantwortlichkeiten ändern sich aus unterschiedlichen Gründen und sollten daher in getrennten Klassen sein. Aufgaben sollten die Tatsache behandeln, dass es sich um eine Aufgabe handelt, und die Daten, die mit einer Aufgabe verknüpft sind. TaskStatePolicy sollte den Übergang von Aufgaben von Status zu Status in einer bestimmten Anwendung behandeln.
quelle
OpenTaskException
(Hinweis, Hinweis) auslösen kann und jedes P eine Ausnahme erwartet, was sagt das dann über den Code zur Schnittstelle aus, nicht über die Implementierung? Worüber rede ich? Ich weiß es nicht. Ich bin nur begeistert, dass ich eine Antwort von Unca 'Bob kommentiere.Dies kann eine Verletzung des LSP sein oder nicht .
Ernsthaft. Lass mich ausreden.
Wenn Sie dem LSP folgen,
ProjectTask
müssen sich Objekte vom TypTask
so verhalten, wie es von Objekten vom Typ erwartet wird.Das Problem mit Ihrem Code ist, dass Sie nicht dokumentiert haben, wie sich Objekte des Typs
Task
voraussichtlich verhalten. Sie haben Code geschrieben, aber keine Verträge. Ich werde einen Vertrag für hinzufügenTask.Close
. Abhängig vom Vertrag, den ich hinzufüge,ProjectTask.Close
folgt der Code für entweder dem LSP oder nicht.In Anbetracht des folgenden Vertrags für Task.Close folgt der Code für
ProjectTask.Close
nicht dem LSP:Angesichts der folgenden Auftrag für Task.Close, den Code für
ProjectTask.Close
sich die LSP wie folgt vor :Methoden, die möglicherweise überschrieben werden, sollten auf zwei Arten dokumentiert werden:
Das "Verhalten" dokumentiert, worauf sich ein Client verlassen kann, der das Empfängerobjekt a kennt
Task
, aber nicht weiß, von welcher Klasse es eine direkte Instanz ist. Außerdem erfahren Designer von Unterklassen, welche Überschreibungen sinnvoll und welche nicht sinnvoll sind.Das "Standardverhalten" dokumentiert, worauf sich ein Client verlassen kann, der weiß, dass das Empfängerobjekt eine direkte Instanz von ist
Task
(dh was Sie erhalten, wenn Sie es verwendennew Task()
. Es teilt den Designern von Unterklassen auch mit, welches Verhalten vererbt wird, wenn dies nicht der Fall ist überschreiben Sie die Methode.Nun sollten folgende Beziehungen gelten:
quelle
Close
wirft die zweite Implementierung von . So erklärt die Unterschrift , dass eine Ausnahme kann ausgelöst werden - es sagt nicht , dass man nicht. Java macht diesbezüglich einen besseren Job. Wenn Sie jedoch deklarieren, dass eine Methode eine Ausnahme deklarieren könnte, sollten Sie die Umstände dokumentieren, unter denen dies möglich ist (oder sein wird). Um sicherzugehen, ob gegen LSP verstoßen wird, ist eine Dokumentation erforderlich, die über die Signatur hinausgeht.if (false) throw new Exception("cannot start")
an die Basisklasse. Der Compiler wird es entfernen, und der Code enthält immer noch, was benötigt wird. Btw. Wir haben immer noch eine LSP-Verletzung mit diesen Workarounds, weil die Vorbedingung immer noch gestärkt ist ...Es ist keine Verletzung des Liskov-Substitutionsprinzips.
Das Liskov-Substitutionsprinzip besagt:
Der Grund, warum Ihre Implementierung des Subtyps nicht gegen das Liskov-Substitutionsprinzip verstößt, ist ganz einfach: Es kann nicht bewiesen werden, was
Task::Close()
tatsächlich geschieht. Sicher,ProjectTask::Close()
wirft eine Ausnahme , wennStatus == Status.Started
, aber so könnteStatus = Status.Closed
inTask::Close()
.quelle
Ja, es ist eine Verletzung.
Ich würde vorschlagen, dass Sie Ihre Hierarchie rückwärts haben. Wenn nicht jeder
Task
verschließbar ist, dannclose()
gehört das nicht reinTask
. Vielleicht möchten Sie eine Schnittstelle,CloseableTask
die alle nichtProjectTasks
implementieren können.quelle
Task
dies nicht selbst implementiertCloseableTask
wird, führen sie eine unsichere Umwandlung durch, um überhaupt aufzurufenClose()
.Es scheint nicht nur ein LSP-Problem zu sein, sondern es werden auch Ausnahmen verwendet, um den Programmfluss zu steuern.
Anscheinend ist dies ein guter Ort, um das Statusmuster für TaskState zu implementieren und die Statusobjekte die gültigen Übergänge verwalten zu lassen.
quelle
Mir fehlt hier eine wichtige Sache in Bezug auf LSP und Design by Contract - in den Voraussetzungen ist es der Anrufer, der dafür verantwortlich ist, sicherzustellen, dass die Voraussetzungen erfüllt sind. Der aufgerufene Code sollte in der DbC-Theorie die Vorbedingung nicht verifizieren. Der Vertrag sollte angeben, wann eine Aufgabe geschlossen werden kann (z. B. CanClose gibt True zurück), und der aufrufende Code sollte sicherstellen, dass die Vorbedingung erfüllt ist, bevor er Close () aufruft.
quelle
ProjectTask
. Dies ist eine Nachbedingung (sie besagt, was nach dem Aufrufen der Methode geschieht), und die Erfüllung dieser Bedingung liegt in der Verantwortung des aufgerufenen Codes.Ja, es ist eine eindeutige Verletzung von LSP.
Einige Leute argumentieren hier, dass es akzeptabel wäre, in der Basisklasse explizit zu machen, dass die Unterklassen Ausnahmen auslösen können, aber ich denke nicht, dass das wahr ist. Unabhängig davon, was Sie in der Basisklasse dokumentieren oder auf welche Abstraktionsebene Sie den Code verschieben, werden die Voraussetzungen in der Unterklasse weiter verbessert, da Sie den Teil "Gestartete Projektaufgabe kann nicht geschlossen werden" hinzufügen. Dies können Sie nicht mit einer Problemumgehung lösen. Sie benötigen ein anderes Modell, das nicht gegen LSP verstößt (oder wir müssen die Einschränkung "Voraussetzungen können nicht verschärft werden" lösen).
Sie können das Dekorationsmuster ausprobieren, wenn Sie in diesem Fall eine LSP-Verletzung vermeiden möchten. Es könnte funktionieren, ich weiß es nicht.
quelle