2009-06-23 3 views

Antwort

52

Ich glaube, die Argumentation so etwas wie dieses:

Angenommen, Sie haben eine Methode, die ein Rechteck annimmt und passt seine Breite:

public void SetWidth(Rectangle rect, int width) 
{ 
    rect.Width = width; 
} 

Es sollte durchaus sinnvoll sein, da, was ein Rechteck hat keinen Einfluss auf seine Höhe

Rectangle rect = new Rectangle(50, 20); // width, height 

SetWidth(rect, 100); 

Assert.AreEqual(20, rect.Height); 

... weil ein Rechteck der Breite zu ändern: anzunehmen, dass dieser Test bestehen würde.

Nehmen wir an, Sie haben eine neue Square-Klasse aus Rectangle abgeleitet. Per Definition hat ein Quadrat Höhe und Breite immer gleich. Lassen Sie uns diesen Test versuchen Sie es erneut:

Rectangle rect = new Square(20); // both width and height 

SetWidth(rect, 100); 

Assert.AreEqual(20, rect.Height); 

Dieser Test wird fehlschlagen, weil Einstellung auch ein Platz der Breite bis 100 wird seine Höhe verändern.

So wird Likovs Substitutionsprinzip durch Ableiten von Square von Rectangle verletzt.

Die "is-a" -Regel macht Sinn in der "realen Welt" (ein Quadrat ist definitiv eine Art Rechteck), aber nicht immer in der Welt des Software-Designs.

bearbeiten

Ihre Frage zu beantworten, ist die richtige Auslegung sollte wahrscheinlich, dass sowohl Rechteck und Quadrat von einem gemeinsamen „Polygon“ oder „Shape“ Klasse abgeleitet werden, die alle Regeln in Bezug auf Breite oder Höhe nicht durchsetzen wird .

+2

Zweites Beispiel, sollte die Bestätigung für 50 testen? BTW, in der "realen Welt" ist ein Quadrat entweder nicht veränderbar (Zeichnet auf einer Seite) oder es kann aufhören, ein Quadrat zu sein, wenn eine Kraft angewendet wird (es besteht aus Gummi), IOW in der realen Welt kann ein Objekt Ändere dynamisch den Typ, den wir in Programmiersprachen nicht gut modellieren. – AnthonyWJones

+1

Hoppla auf dem zweiten Beispiel gut entdeckt. Der Test ist richtig - der Ctor-Parameter ist falsch. Zwischenablage-Erbenfehler! Ich werde es jetzt reparieren. –

-3

Es ist ziemlich einfach :) Je mehr 'Basis' sollte die Klasse (die erste in der Ableitung Kette) die allgemeinste sein.

Zum Beispiel Form -> Rechteck -> Quadrat.

Hier ist ein Quadrat ein Sonderfall eines Rechtecks ​​(mit eingeschränkten Dimensionen) und ein Rechteck ist ein Sonderfall einer Form.

Anders gesagt - verwenden Sie den "ist ein" Test. Ein Knappe ist ein Rechteck. Aber ein Rechteck ist nicht immer ein Quadrat.

+3

Sie haben den Punkt in der Frage in Bezug auf das "Liskov-Substitutionsprinzip" verpasst. Siehe http://www.objectmentor.com/resources/articles/lsp.pdf, in dem erklärt wird, dass der 'ist ein' Test in Bezug auf dieses Prinzip für den Fall von Rechteck-> Quadrat nicht angemessen ist. Die Antwort ist nicht so einfach. (Ich habe dich nicht markiert). –

2

Nehmen wir an, wir haben die Klasse Rectangle mit den beiden (für die Einfachheit öffentlichen) Eigenschaften Breite, Höhe. Wir können diese beiden Eigenschaften ändern: r.width = 1, r.height = 2.
Jetzt sagen wir ein Quadrat is_a Rectangle. Aber obwohl die Behauptung "ein Quadrat verhält sich wie ein Rechteck" ist, können wir nicht .width = 1 und .height = 2 auf einem quadratischen Objekt einstellen (Ihre Klasse passt wahrscheinlich die Breite an, wenn Sie die Höhe einstellen und umgekehrt). Es gibt also mindestens einen Fall, in dem sich ein Objekt vom Typ Square nicht wie ein Rechteck verhält und Sie es daher nicht (vollständig) ersetzen können.

61

Die Antwort hängt von der Wandlungsfähigkeit ab.Wenn Ihre Rechteck- und Quadratklassen unveränderlich sind, dann ist Square wirklich ein Subtyp von Rectangle und es ist völlig in Ordnung, zuerst von Sekunde abzuleiten. Andernfalls könnten Rectangle und Square beide einen IRectangle ohne Mutatoren freilegen, aber Ableiten von einem anderen ist falsch, da keiner der beiden Typen richtig ein Untertyp des anderen ist.

+9

+1, YEP! Ich sage das seit über 20 Jahren und kaum jemand hat zugehört ... –

+1

Guter Punkt da mit Mutabilität. – Gishu

+0

Perzeptiv. Ich nehme an, Sie könnten etwas weiter verallgemeinern, indem Sie sagen, dass die Wahrheit der Aussage "A ist ein Untertyp von B" von der öffentlichen Schnittstelle von B abhängt. Eine kleinere Schnittstelle (zB eine Schnittstelle ohne Mutatoren) reduziert die Menge, die Sie "tun" können. mit konkreten Objekten dieses Typs oder seiner Subtypen, aber gibt Ihnen mehr Möglichkeiten zum Subtyping. –

3

Ich stimme nicht überein, dass die Ableitung von Quadrat aus Rechteck notwendigerweise LSP verletzt.

In Matts Beispiel, wenn Sie Code haben, der davon abhängig ist, dass Breite und Höhe unabhängig sind, verletzt er tatsächlich LSP.

Wenn Sie jedoch ein Rechteck für ein Quadrat überall in Ihrem Code ersetzen können, ohne Annahmen zu verletzen, dann verletzen Sie nicht LSP.

Es kommt also wirklich darauf an, was das Abstraktionsrechteck in Ihre Lösung bedeutet.

+1

Das ist richtig, aber es ist sinnlos, eine Square-Klasse zu haben, es sei denn, Sie verwenden seine Invarianten im Programm :) –

+0

Ich könnte mir vorstellen, eine Codebasis zu haben, die auf Bildrechtecken funktioniert. Dann erhalten Sie eine Idee für eine Leistungsoptimierung, wenn die Rechtecke quadratisch sind. Also ja, Sie verwenden die Breite/Höhe-Invariante intern im Quadrat, aber sie ist für die Außenwelt unsichtbar (Nur wenn im Code natürlich keine Annahmen über die Orthogonalität von Breite und Höhe getroffen werden). –

+0

Ich stimme dem zu, aber dies gilt nicht für den Fall, dass jemand anders Rectangle * Invarianten verwendet, wie sie von Matt oben vorgeschlagen wurden. Das habe ich nicht bemerkt, als ich den ersten Kommentar geschrieben habe. –

1

Ich glaube, dass OOD/OOP-Techniken existieren, damit Software die reale Welt darstellen kann. In der realen Welt ist ein Quadrat ein Rechteck, das gleiche Seiten hat. Das Quadrat ist nur ein Quadrat, weil es die gleichen Seiten hat, nicht weil es sich für ein Quadrat entschieden hat. Daher muss das OO-Programm damit umgehen. Wenn die Routine, die das Objekt instanziiert, es quadratisch möchte, könnte sie natürlich die Längeneigenschaft und die Breiteneigenschaft als gleich groß angeben. Wenn das Programm, das das Objekt verwendet, später wissen muss, ob es quadratisch ist, muss es es nur fragen. Das Objekt könnte eine schreibgeschützte Boolesche Eigenschaft namens "Square" haben. Wenn die aufrufende Routine sie aufruft, kann das Objekt zurückkehren (Länge = Breite). Dies kann auch dann der Fall sein, wenn das Rechteckobjekt unveränderlich ist. Wenn das Rechteck tatsächlich unveränderbar ist, kann der Wert der Eigenschaft Square im Konstruktor festgelegt und damit ausgeführt werden. Warum ist das dann ein Problem? Der LSP erfordert, dass Unterobjekte unveränderlich angewendet werden und das Quadrat ein Unterobjekt eines Rechtecks ​​ist, wird oft als ein Beispiel für seine Verletzung verwendet. Aber das scheint kein gutes Design zu sein, denn wenn die Using-Routine das Objekt als "objSquare" aufruft, muss sie ihre inneren Details kennen. Wäre es nicht besser, wenn es egal wäre, ob das Rechteck quadratisch ist oder nicht? Und das wäre, weil die Methoden des Rechtecks ​​unabhängig davon korrekt wären. Gibt es ein besseres Beispiel für die Verletzung des LSP?

Eine weitere Frage: Wie wird ein Objekt unveränderlich gemacht? Gibt es eine "Immutable" -Eigenschaft, die bei Instanziierung gesetzt werden kann?

Ich fand die Antwort und es ist, was ich erwartet habe. Da ich ein VB .NET-Entwickler bin, interessiert mich das. Aber die Konzepte sind in allen Sprachen gleich. In VB .NET erstellen Sie unveränderliche Klassen, indem Sie die Eigenschaften schreibgeschützt machen und den Konstruktor Neu verwenden, damit die Instanziierungsroutine beim Erstellen des Objekts Eigenschaftswerte angeben kann. Sie können für einige Eigenschaften auch Konstanten verwenden, die immer gleich sind. Von der Schöpfung an ist das Objekt unveränderlich.

-1

Das Problem ist, dass das, was beschrieben wird, wirklich kein "Typ" ist, sondern eine kumulative emergente Eigenschaft.

Alles, was Sie wirklich haben, ist ein Viereck, und sowohl "Rechtwinkligkeit" als auch "Rechteckigkeit" sind nur emergente Artefakte, die von den Eigenschaften der Winkel und Seiten abgeleitet sind.

Das gesamte Konzept des „Square“ (oder Rechtecks ​​selbst) ist nur eine abstrakte Darstellung einer Sammlung von Eigenschaften des Objekts in Bezug zueinander und das Objekt in Frage, nicht die Art von Objekt in und es ist selbst .

Hier hilft das Nachdenken über das Problem im Zusammenhang mit einer typlosen Sprache, weil es nicht der Typ ist, der bestimmt, ob es ein Quadrat ist, sondern die tatsächlichen Eigenschaften des Objekts, die bestimmen, ob es ein Quadrat ist.

Ich denke, wenn Sie die Abstraktion noch weiter nehmen wollen, würden Sie nicht einmal sagen, dass Sie ein Viereck haben, sondern dass Sie ein Vieleck oder sogar nur eine Form haben.

3

Ich habe in letzter Zeit viel mit diesem Problem zu kämpfen und dachte, dass ich meinen Hut in den Ring hinzufügen würde:

public class Rectangle { 

    protected int height;  
    protected int width; 

    public Rectangle (int height, int width) { 
     this.height = height; 
     this.width = width; 
    } 

    public int computeArea() { return this.height * this.width; } 
    public int getHeight() { return this.height; } 
    public int getWidth() { return this.width; } 

} 

public class Square extends Rectangle { 

    public Square (int sideLength) { 
     super(sideLength, sideLength); 
    } 

} 

public class ResizableRectangle extends Rectangle { 

    public ResizableRectangle (int height, int width) { 
     super(height, width); 
    } 

    public void setHeight (int height) { this.height = height; } 
    public void setWidth (int width) { this.width = width; } 

} 

Beachten Sie die letzte Klasse, ResizableRectangle. Indem wir die "Resizabilität" in eine Unterklasse verschieben, bekommen wir Code wiederverwendet, während wir unser Modell verbessern. Stellen Sie sich folgendes vor: Ein Quadrat kann nicht frei skaliert werden, während es ein Quadrat bleibt, während nicht quadratische Rechtecke es können. Nicht Alle Rechtecke können jedoch in der Größe geändert werden, da ein Quadrat ein Rechteck ist (und es kann nicht frei skaliert werden, während seine "Identität" beibehalten wird). (o_O) Es ist also sinnvoll, eine Basisklasse Rectangle zu erstellen, die nicht skalierbar ist, da dies eine zusätzliche Eigenschaft von einigen Rechtecken ist.

Verwandte Themen