2012-05-28 6 views
13

Ich bin in einer Situation sehr ähnlich zu dem, was Steve McConnell's in Code Complete erwähnt hat. Nur dass mein Problem auf Fahrzeugen beruht und Trike zufällig auf dem steht, fällt per Gesetz in die Kategorie der Autos. Autos hatten bis jetzt vier Räder. Irgendwie ist meine Domain unnötig komplex, so dass es einfach ist, unten bei Katzen zu bleiben.Wie kann ich das Liskow Substitutionsprinzip (LSP) vermeiden?

verdächtig Klassen sein, die eine Routine außer Kraft setzen und bei der Gestaltung von die Basisklasse Dies zeigt normalerweise einen Fehler nichts innerhalb die abgeleitete Routine tun. Angenommen, Sie haben eine Klasse Cat und eine Routine Scratch() und nehmen an, dass Sie irgendwann herausfinden, dass einige Katzen deklatiert sind und nicht kratzen können. Sie könnten versucht sein, eine Klasse aus Cat mit dem Namen ScratchlessCat zu erstellen und die Scratch() Routine zu überschreiben, um nichts zu tun. Dieser Ansatz weist mehrere Probleme auf:

Es verletzt die Abstraktion (Schnittstellenvertrag), die in der Kategorie Cat präsentiert wird, durch Ändern der Semantik seiner Schnittstelle.

Dieser Ansatz gerät schnell außer Kontrolle, wenn Sie ihn auf andere abgeleitete Klassen erweitern. Was passiert, wenn du eine Katze ohne Schwanz findest? Oder eine Katze, die keine Mäuse fängt? Oder eine Katze, die keine Milch trinkt? Schließlich werden Sie mit abgeleiteten Klassen wie ScratchlessTaillessMicelessMilklessCat enden.

Im Laufe der Zeit führt dieser Ansatz zu Code, der für verwirrend ist, da die Schnittstellen und das Verhalten der Vorgängerklassen wenig oder nichts über das Verhalten ihrer Nachkommen implizieren.

Der Ort, um dieses Problem zu beheben, ist nicht in der Basisklasse, sondern in der ursprünglichen Cat-Klasse . Erstellen Sie eine Claws-Klasse und enthalten Sie diese innerhalb der Cats-Klasse. Das Grundproblem war die Annahme, dass alle Katzen kratzen, so beheben Sie dieses Problem an der Quelle, anstatt nur bei das Ziel zu verbinden.

Nach dem Text aus seinem großen Buch oben. Im Anschluss ist schlecht

Geordnete Klasse nicht

public abstract class Cat { 
    public void scratch() { 
     System.out.println("I can scratch"); 
    } 
} 

Abgeleitete Klasse

public class ScratchlessCat extends Cat { 
    @Override 
    public void scratch() { 
     // do nothing 
    } 
} 

Jetzt abstrakt zu sein braucht er eine andere Klasse schlägt Claws zu schaffen, aber ich verstehe nicht, wie kann Ich verwende diese Klasse, um die Notwendigkeit ScratchlessCat#Scratch zu vermeiden.

+2

Das Cat-Beispiel ist eine gute Illustration des LSP. Als ich etwas über LSP lernte, war ich erstaunt, wie viele schlechte Beispiele und wie viele Missverständnisse es auf SO und anderen Seiten gibt. – SteveT

Antwort

8

Sie würden nach wie vor eine scratch() Methode, aber es wird nicht von den abgeleiteten Klassen überschrieben werden:

public class Cat { 
    Claw claw_; 
    public Cat(Claw claw) {claw = claw_;} 
    public final void scratch() { 
    if (claw_ != null) { 
     claw_.scratch(this); 
    } 
    } 
} 

Dieses Sie die Kratzen Logik auf die enthaltenen Claw Objekt, falls vorhanden (und nicht neu zu delegieren erlaubt wenn es keine Krallen gibt). Von cat abgeleitete Klassen haben keinen Einfluss auf das Scratchen, also müssen keine Schattenhierarchien basierend auf Fähigkeiten erstellt werden.

Da die abgeleiteten Klassen die Methodenimplementierung nicht ändern können, besteht auch kein Problem darin, die beabsichtigte Semantik der scratch()-Methode in der Schnittstelle der Basisklasse zu unterbrechen.

Wenn Sie dies zu den Extremen bringen, können Sie feststellen, dass Sie viele Klassen und nicht viele Ableitungen haben - die meiste Logik wird an die Kompositionsobjekte delegiert, nicht den abgeleiteten Klassen anvertraut.

11

Dass nicht alle Katzen Krallen haben und kratzen können, ist ein großer Hinweis, dass Cat in seiner API keine öffentliche scratch Methode definieren sollte. Der erste Schritt ist zu überlegen, warum Sie zuerst scratch definiert haben. Vielleicht kratzen Katzen, wenn sie können, wenn sie angegriffen werden; wenn nicht, zischen sie oder rennen davon.

public class Cat extends Animal { 
    private Claws claws; 

    public void onAttacked(Animal attacker) { 
     if (claws != null) { 
      claws.scratch(attacker); 
     } 
     else { 
      // Assumes all cats can hiss. 
      // This may also be untrue and you need Voicebox. 
      // Rinse and repeat. 
      hiss(); 
     } 
    } 
} 

Jetzt können Sie einen beliebigen Cat Unterklasse für eine andere ersetzen, und es verhält sich richtig je nachdem, ob es nicht Krallen hat. Sie könnten eine DefenseMechanism Klasse definieren, um die verschiedenen Abwehrmechanismen wie Scratch, Hiss, Bite usw. zu organisieren.

Verwandte Themen