Ich habe kürzlich eine Codebasis geerbt, die einige wichtige Liskov-Übertreter enthält. In wichtigen Klassen. Das hat mir große Schmerzen bereitet. Lass mich erklären warum.
Ich habe Class A
, was davon herrührt Class B
. Class A
und Class B
eine Reihe von Eigenschaften gemeinsam nutzen, Class A
die die eigene Implementierung überschreiben. Das Festlegen oder Abrufen einer Class A
Eigenschaft hat andere Auswirkungen als das Festlegen oder Abrufen derselben Eigenschaft Class B
.
public Class A
{
public virtual string Name
{
get; set;
}
}
Class B : A
{
public override string Name
{
get
{
return TranslateName(base.Name);
}
set
{
base.Name = value;
FunctionWithSideEffects();
}
}
}
Abgesehen von der Tatsache, dass dies eine äußerst schreckliche Methode für die Übersetzung in .NET ist, gibt es eine Reihe weiterer Probleme mit diesem Code.
In diesem Fall Name
wird an mehreren Stellen ein Index und eine Flusssteuerungsvariable verwendet. Die oben genannten Klassen sind in der gesamten Codebasis sowohl in ihrer rohen als auch in ihrer abgeleiteten Form übersät. Wenn ich in diesem Fall gegen das Liskov-Substitutionsprinzip verstoße, muss ich den Kontext jedes einzelnen Aufrufs jeder der Funktionen kennen, die die Basisklasse verwenden.
Der Code verwendet Objekte von beiden Class A
und Class B
, so dass ich nicht einfach Class A
abstrakt machen kann , um Leute zur Verwendung zu zwingen Class B
.
Es gibt einige sehr nützliche Dienstprogrammfunktionen, die ausgeführt werden, Class A
und andere sehr nützliche Dienstprogrammfunktionen, die ausgeführt werden Class B
. Im Idealfall würde Ich mag jede Nutzenfunktion verwenden können , die arbeiten auf können Class A
auf Class B
. Viele der Funktionen, die a Class B
benötigen, könnten a ohne Class A
die Verletzung des LSP problemlos in Anspruch nehmen.
Das Schlimmste daran ist, dass sich dieser spezielle Fall nur schwer umgestalten lässt, da die gesamte Anwendung von diesen beiden Klassen abhängt, die ganze Zeit auf beiden Klassen ausgeführt wird und in hundertfacher Hinsicht einen Bruch darstellt, wenn ich dies ändere (was ich tun werde) sowieso).
Um dies zu beheben, muss ich eine NameTranslated
Eigenschaft erstellen , die die Class B
Version der Name
Eigenschaft ist, und jeden Verweis auf die abgeleitete Name
Eigenschaft sehr, sehr sorgfältig ändern , um meine neue NameTranslated
Eigenschaft zu verwenden. Wenn jedoch auch nur eine dieser Referenzen falsch ist, kann die gesamte Anwendung in die Luft gehen.
Da es in der Codebasis keine Komponententests gibt, ist dies beinahe das gefährlichste Szenario, dem ein Entwickler begegnen kann. Wenn ich die Verletzung nicht ändere, muss ich große Mengen geistiger Energie aufwenden, um zu verfolgen, welcher Objekttyp bei jeder Methode bearbeitet wird, und wenn ich die Verletzung behebe, kann das gesamte Produkt zu einem ungünstigen Zeitpunkt explodieren.