Angenommen, ich habe eine Schnittstelle FooInterface
mit der folgenden Signatur:
interface FooInterface {
public function doSomething(SomethingInterface something);
}
Und eine konkrete Klasse ConcreteFoo
, die diese Schnittstelle implementiert:
class ConcreteFoo implements FooInterface {
public function doSomething(SomethingInterface something) {
}
}
Ich möchte ConcreteFoo::doSomething()
etwas Einzigartiges tun, wenn es einem bestimmten SomethingInterface
Objekttyp übergeben wird (sagen wir, es heißt SpecialSomething
).
Es ist definitiv eine LSP-Verletzung, wenn ich die Voraussetzungen der Methode verbessere oder eine neue Ausnahme auslöse. Wäre es dennoch eine LSP-Verletzung, wenn ich SpecialSomething
Objekte mit einem speziellen Gehäuse ausführe und gleichzeitig einen Fallback für generische SomethingInterface
Objekte bereitstelle ? So etwas wie:
class ConcreteFoo implements FooInterface {
public function doSomething(SomethingInterface something) {
if (something instanceof SpecialSomething) {
// Do SpecialSomething magic
}
else {
// Do generic SomethingInterface magic
}
}
}
doSomething()
Methode die Typkonvertierung inSpecialSomething
: Wenn sie empfangenSpecialSomething
würde, würde sie das Objekt nur unverändert zurückgeben, während sie bei Empfang eines generischenSomethingInterface
Objekts einen Algorithmus ausführen würde, um es in einSpecialSomething
Objekt zu konvertieren . Da die Vor- und Nachbedingungen gleich bleiben, glaube ich nicht, dass der Vertrag verletzt wurde.y = doSomething(x)
, dannx.setFoo(3)
, und findet dann , dass diey.getFoo()
Rendite 3?SpecialSomething
stattdessen eine Kopie des Objekts zurückgeben. Obwohl der Reinheit halber, konnte ich auch sehen, dass die Sonderfalloptimierung beim Übergeben des Objekts wegfielSpecialSomething
und nur den größeren Konvertierungsalgorithmus durchlief, da es immer noch funktionieren sollte, da es auch einSomethingInterface
Objekt ist.Es ist keine Verletzung des LSP. Es ist jedoch immer noch ein Verstoß gegen die Regel "Keine Typprüfung durchführen". Es wäre besser, den Code so zu gestalten, dass das Besondere natürlich entsteht. Vielleicht
SomethingInterface
braucht man ein anderes Mitglied, das dies erreichen könnte, oder man muss irgendwo eine abstrakte Fabrik einbauen.Dies ist jedoch keine feste Regel, daher müssen Sie entscheiden, ob sich der Kompromiss lohnt. Im Moment haben Sie einen Codegeruch und ein mögliches Hindernis für zukünftige Verbesserungen. Es loszuwerden, könnte eine wesentlich komplexere Architektur bedeuten. Ohne weitere Informationen kann ich nicht sagen, welches besser ist.
quelle
Nein, wenn Sie die Tatsache ausnutzen, dass ein gegebenes Argument nicht nur die Schnittstelle A, sondern auch A2 bereitstellt, wird der LSP nicht verletzt.
Stellen Sie nur sicher, dass der spezielle Pfad keine stärkeren Vorbedingungen (abgesehen von denjenigen, die bei der Entscheidung für ihn getestet wurden) und keine schwächeren Nachbedingungen aufweist.
C ++ - Vorlagen tun dies häufig, um eine bessere Leistung zu erzielen, z. B. indem sie
InputIterator
s erfordern , aber zusätzliche Garantien geben, wenn sie mitRandomAccessIterator
s aufgerufen werden .Wenn Sie sich stattdessen zur Laufzeit entscheiden müssen (z. B. mithilfe von dynamischem Casting), müssen Sie sich vor der Entscheidung hüten, welchen Weg Sie einschlagen, um all Ihre potenziellen Gewinne oder noch mehr zu verbrauchen.
Das Ausnutzen des Sonderfalls wirkt sich häufig negativ auf DRY aus (wiederholen Sie sich nicht), da Sie möglicherweise Code duplizieren müssen, und auf KISS (Keep it Simple), da dieser komplexer ist.
quelle
Es gibt einen Kompromiss zwischen dem Prinzip "Keine Typprüfungen durchführen" und "Schnittstellen trennen". Wenn viele Klassen ein praktikables, aber möglicherweise ineffizientes Mittel zum Ausführen einiger Aufgaben bereitstellen, und einige von ihnen auch ein besseres Mittel bereitstellen können, besteht ein Bedarf an Code, der alle Elemente der breiteren Kategorie akzeptieren kann, die die Aufgabe ausführen können (Vielleicht ineffizient) Wenn Sie die Aufgabe jedoch so effizient wie möglich ausführen möchten, müssen entweder alle Objekte eine Schnittstelle implementieren, die ein Mitglied enthält, das angibt, ob die effizientere Methode unterstützt wird, oder eine andere, die sie verwendet, wenn dies der Fall ist Lassen Sie den Code, der das Objekt empfängt, prüfen, ob es eine erweiterte Schnittstelle unterstützt, und setzen Sie sie gegebenenfalls um.
Persönlich bevorzuge ich den ersteren Ansatz, obwohl ich mir wünsche, dass objektorientierte Frameworks wie .NET es Schnittstellen ermöglichen würden, Standardmethoden anzugeben (wodurch die Arbeit mit größeren Schnittstellen weniger schmerzhaft wird). Wenn die allgemeine Schnittstelle optionale Methoden enthält, kann eine einzelne Wrapper-Klasse Objekte mit vielen verschiedenen Fähigkeitskombinationen verarbeiten, wobei nur die Fähigkeiten für Verbraucher in Frage kommen, die im ursprünglichen Wrapper-Objekt vorhanden sind. Wenn viele Funktionen auf verschiedene Schnittstellen aufgeteilt sind, wird für jede andere Kombination von Schnittstellen, die umgebrochene Objekte möglicherweise unterstützen müssen, ein anderes Wrapper-Objekt benötigt.
quelle
Beim Liskov-Substitutionsprinzip handelt es sich um Subtypen, die gemäß einem Vertrag ihres Supertyps handeln. Wie Ixrec schrieb, gibt es nicht genügend Informationen, um zu beantworten, ob es sich um eine LSP-Verletzung handelt.
Was hier jedoch verletzt wird, ist ein offenes geschlossenes Prinzip. Wenn Sie eine neue Anforderung haben - Do SpecialSomething Magic - und Sie vorhandenen Code ändern müssen, verstoßen Sie definitiv gegen OCP .
quelle