valuacion sustitucion solid segregación principios principio matematica logica liskov interfaces ejemplos economia devexperto design oop lsp

design - sustitucion - ¿Derivar cuadrado del rectángulo es una violación del Principio de Sustitución de Liskov?



principio de sustitucion valuacion (8)

Creo que el razonamiento es algo como esto:

Supongamos que tiene un método que acepta un rectángulo y ajusta su ancho:

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

Debería ser perfectamente razonable, dado lo que es un rectángulo, suponer que pasaría esta prueba:

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 deriva una nueva clase Cuadrado de Rectángulo. Por definición, un cuadrado tiene alto y ancho siempre iguales. Probemos nuevamente esa prueba:

Rectangle rect = new Square(20); // both width and height SetWidth(rect, 100); Assert.AreEqual(20, rect.Height);

Esa prueba fallará, porque establecer el ancho de un cuadrado a 100 también cambiará su altura.

Por lo tanto, el principio de sustitución de Liskov se viola al derivar Square de Rectangle.

La regla "es-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, probablemente el diseño correcto sea que tanto Rectángulo como Cuadrado deriven de una clase común "Polígono" o "Forma", que no impone ninguna regla con respecto al ancho o la altura.

Soy nuevo para diseñar y aprender los principios de diseño.

Dice que derivar cuadrado de rectángulo es un ejemplo clásico de violación del Principio de Sustitución de Liskov.

Si ese es el caso, ¿cuál debería ser el diseño correcto?


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.


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

Todo lo que realmente tienes 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 simplemente 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 tipo 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.


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.


He estado luchando con este problema mucho últimamente y pensé en agregar mi sombrero al ring:

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

Observe 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. Sin embargo, no todos los rectángulos pueden redimensionarse, 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 Rectangle base que no sea redimensionable, ya que esta es una propiedad extra de algunos rectángulos.


La respuesta depende de la mutabilidad. Si las clases rectangulares y 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 correctamente un subtipo del otro.


No estoy de acuerdo en que derivar un cuadrado de un rectángulo necesariamente viole 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.

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


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 cuadrado 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).