2009-06-23 11 views

Respuesta

52

Creo que el razonamiento es algo como esto:

Digamos que usted tiene un método que acepta un rectángulo y ajusta su anchura:

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

Cabe perfectamente razonable, dado que un rectángulo es , para suponer que esta prueba pasaría:

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

SetWidth(rect, 100); 

Assert.AreEqual(20, rect.Height); 

... porque cambiar el ancho de un rectángulo no afecta su altura.

Sin embargo, supongamos que ha obtenido una nueva clase Cuadrado de Rectángulo. Por definición, un cuadrado tiene alto y ancho siempre iguales. Vamos a tratar que prueba de nuevo:

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

SetWidth(rect, 100); 

Assert.AreEqual(20, rect.Height); 

Esa prueba fallará, ya que el establecimiento de la anchura de un cuadrado a 100 también cambiará su altura.

Por lo tanto, el principio de sustitución de Liskov se infringe derivando Square from Rectangle.

La regla "is-a" tiene sentido en el "mundo real" (un cuadrado es definitivamente un tipo de rectángulo), pero no siempre en el mundo del diseño de software.

Editar

Para responder a su pregunta, el diseño correcto probablemente debería ser que tanto rectángulo y de la plaza se derivan de un "Polígono" común o una clase de "Forma", que no hace cumplir las normas relativas a la anchura o altura .

+2

Segundo ejemplo, si el assert está probando 50? Por cierto, en el "mundo real" un cuadrado no es mutable (se dibuja en una página) o puede dejar de ser un cuadrado cuando se aplica una fuerza (está hecho de caucho), IOW en el mundo real un objeto puede cambie el tipo dinámicamente, algo que no modelamos muy bien en los lenguajes de programación. – AnthonyWJones

+1

Vaya bien visto en el segundo ejemplo. La prueba es correcta: el parámetro ctor es incorrecto. ¡Error de herencia de portapapeles! Lo arreglaré ahora. –

-3

Es bastante simple :) Cuanto más 'base' la clase (la primera en la cadena de derivación) debería ser la más general.

Por ejemplo, forma -> Rectángulo -> Cuadrado.

Aquí un cuadrado es un caso especial de un rectángulo (con dimensiones restringidas) y un rectángulo es un caso especial de una forma.

Dicho de otra manera: use la prueba "es una". Un escudero es un rectángulo. Pero una redecilla no siempre es un cuadrado.

+3

Ha perdido el punto en la pregunta con respecto al "principio de sustitución Liskov". Ver http://www.objectmentor.com/resources/articles/lsp.pdf que explica cómo la prueba "es una" no es adecuada con respecto a este principio para el caso de rectangle-> square. La respuesta no es tan simple'. (No te marqué) –

2

Supongamos que tenemos la clase Rectángulo con las dos (por simplicidad pública) ancho y altura de las propiedades. Podemos cambiar esas dos propiedades: r.width = 1, r.height = 2.
Ahora decimos un rectángulo is_a Rectangle. Pero aunque la afirmación es "un cuadrado se comportará como un rectángulo" no podemos establecer .width = 1 y .height = 2 en un objeto cuadrado (su clase probablemente ajusta el ancho si establece la altura y viceversa). Por lo tanto, hay al menos un caso en el que un objeto de tipo Square no se comporta como un rectángulo y, por lo tanto, no puede sustituirlo (completamente).

61

La respuesta depende de la mutabilidad.Si su rectángulo y sus clases cuadradas son inmutables, entonces Square es realmente un subtipo de Rectangle y está perfectamente bien derivar primero de segundo. De lo contrario, Rectangle y Square podrían exponer un IRectangle sin mutadores, pero derivar uno del otro es incorrecto ya que ninguno de los tipos es propiamente un subtipo del otro.

+9

+1, YEP! He estado diciendo esto por más de 20 años y casi nadie ha estado escuchando ... –

+1

Buen punto con mutabilidad. – Gishu

+0

Perceptivo. Supongo que podría generalizar un poco más diciendo que la verdad de la afirmación "A es un subtipo de B" depende de la interfaz pública expuesta por B. Una interfaz más pequeña (por ejemplo, una interfaz que no tiene mutadores) reduce la cantidad que puede "hacer" con objetos concretos de ese tipo o sus subtipos, pero le da más oportunidades para subtipar. –

3

No estoy de acuerdo con que la derivación de un cuadrado desde un rectángulo viole necesariamente el LSP.

En el ejemplo de Matt, si tiene un código que depende de que el ancho y la altura sean independientes, entonces de hecho viola el LSP.

Sin embargo, si puede sustituir un rectángulo por un cuadrado en cualquier lugar de su código sin romper ninguna suposición, entonces no está violando el LSP.

Así que realmente se reduce a lo que significa el rectángulo de abstracción en su solución.

+1

Esto es correcto, pero es inútil tener una clase Square a menos que use sus invariantes en el programa :) –

+0

Me podría imaginar tener una base de código que funciona en rectángulos de imagen. Luego se obtiene una idea para una optimización del rendimiento si los rectángulos son cuadrados. Así que sí, estás utilizando la invariante de ancho/alto internamente en el cuadrado, pero es invisible para el mundo exterior (solo si no se hacen suposiciones en el código sobre la ortogonalidad de ancho y alto, por supuesto). –

+0

Estoy de acuerdo, pero esto no cubre el caso de otra persona que usa invariantes * Rectangle *, como los sugeridos por Matt arriba. No me di cuenta de esto cuando escribí el primer comentario. –

1

Creo que existen técnicas OOD/OOP para permitir que el software represente el mundo real. En el mundo real, un cuadrado es un rectángulo que tiene lados iguales. El cuadrado es un cuadrado solo porque tiene lados iguales, no porque decidió ser un cuadrado. Por lo tanto, el programa OO necesita lidiar con eso. Por supuesto, si la rutina instanciando el objeto quiere que sea cuadrado, podría especificar que la propiedad de longitud y la propiedad de ancho sean iguales a la misma cantidad. Si el programa que usa el objeto necesita saber más tarde si es cuadrado, solo necesita preguntarlo. El objeto podría tener una propiedad booleana de solo lectura llamada "Cuadrado". Cuando la rutina de llamada lo invoca, el objeto puede regresar (Longitud = Ancho). Ahora bien, este puede ser el caso incluso si el objeto rectángulo es inmutable. Además, si el rectángulo es realmente inmutable, el valor de la propiedad Square se puede establecer en el constructor y se puede completar con él. ¿Por qué entonces esto es un problema? El LSP requiere que los subobjetos sean inmutables para aplicar y que el cuadrado sea un subobjeto de un rectángulo se use a menudo como ejemplo de su violación. Pero eso no parece ser un buen diseño porque cuando la rutina de uso invoca el objeto como "objSquare", debe conocer su detalle interno. ¿No sería mejor si no le importara si el rectángulo era cuadrado o no? Y eso sería porque los métodos del rectángulo serían correctos independientemente. ¿Hay un mejor ejemplo de cuándo se viola el LSP?

Una pregunta más: ¿cómo se hace un objeto inmutable? ¿Hay una propiedad "Inmutable" que se pueda establecer en instanciación?

Encontré la respuesta y es lo que esperaba. Como soy desarrollador de VB .NET, eso es lo que me interesa. Pero los conceptos son los mismos en todos los idiomas. En VB .NET crea clases inmutables al hacer que las propiedades sean de solo lectura y usa el constructor Nuevo para permitir que la rutina de creación de instancias especifique valores de propiedad cuando se crea el objeto. También puede usar constantes para algunas de las propiedades y siempre serán las mismas. Desde la creación hacia adelante, el objeto es inmutable.

-1

El problema es que lo que se describe no es realmente un "tipo" sino una propiedad emergente acumulativa.

Todo lo que realmente tiene es un cuadrilátero y que tanto "cuadratura" como "rectitud" son solo artefactos emergentes derivados de las propiedades de los ángulos y los lados.

El concepto completo de "Cuadrado" (o incluso rectángulo) es solo una representación abstracta de una colección de propiedades del objeto en relación entre sí y el objeto en cuestión, no el tipo de objeto en y de sí mismo .

Aquí es donde pensar en el problema en el contexto de un lenguaje sin letra puede ayudar, porque no es el tipo que determina si es "un cuadrado" sino las propiedades reales del objeto que determina si es "un cuadrado".

Supongo que si quieres llevar la abstracción aún más lejos, ni siquiera dirías que tienes un cuadrilátero, pero que tienes un polígono o incluso una forma.

3

He estado luchando con este problema mucho últimamente y que me gustaría añadir mi sombrero en el anillo:

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; } 

} 

Aviso de la última clase, ResizableRectangle. Al mover la "capacidad de redimensionamiento" a una subclase, obtenemos la reutilización del código al mismo tiempo que mejoramos nuestro modelo. Piénselo de esta manera: un cuadrado no se puede cambiar de tamaño libremente sin dejar de ser un cuadrado, mientras que los rectángulos no cuadrados sí lo pueden hacer. No todos los rectángulos se pueden cambiar de tamaño, ya que un cuadrado es un rectángulo (y no se puede cambiar de tamaño libremente conservando su "identidad"). (o_O) Por lo tanto, tiene sentido hacer una clase base Rectangle que no se puede cambiar de tamaño, ya que esta es una propiedad adicional de rectángulos.

Cuestiones relacionadas