Si un cuadrado es un tipo de rectángulo, ¿por qué no puede heredar un cuadrado de un rectángulo? ¿O por qué es un mal diseño?
He escuchado a gente decir:
Si hiciste que Square derivara de Rectángulo, entonces un Square debería poder usarse en cualquier lugar donde esperes un rectángulo
¿Cuál es el problema aquí? ¿Y por qué Square sería utilizable en cualquier lugar donde espere un rectángulo? Solo sería utilizable si creamos el objeto Square, y si anulamos los métodos SetWidth y SetHeight para Square, ¿por qué habría algún problema?
Si tenía los métodos SetWidth y SetHeight en su clase base Rectangle y su referencia Rectangle apuntaba a un cuadrado, entonces SetWidth y SetHeight no tienen sentido porque establecer uno cambiaría el otro para que coincida. En este caso, Square falla la prueba de sustitución de Liskov con rectángulo y la abstracción de que Square herede de rectángulo es mala.
¿Alguien puede explicar los argumentos anteriores? Nuevamente, si anulamos los métodos SetWidth y SetHeight en Square, ¿no resolvería este problema?
También he escuchado / leído:
El verdadero problema es que no estamos modelando rectángulos, sino más bien "rectángulos reestructurables", es decir, rectángulos cuyo ancho o alto se pueden modificar después de la creación (y todavía consideramos que es el mismo objeto). Si nos fijamos en la clase de rectángulo de esta manera, está claro que un cuadrado no es un "rectángulo reestructurable", porque un cuadrado no puede ser reformado y sigue siendo un cuadrado (en general). Matemáticamente, no vemos el problema porque la mutabilidad ni siquiera tiene sentido en un contexto matemático.
Aquí creo que "redimensionable" es el término correcto. Los rectángulos son "redimensionables" y también lo son los cuadrados. ¿Me estoy perdiendo algo en el argumento anterior? Un cuadrado puede redimensionarse como cualquier rectángulo.
fuente
Why do we even need Square
? Es como tener dos plumas. Un bolígrafo azul y uno rojo, azul, amarillo o verde. El bolígrafo azul es redundante, incluso más en el caso del cuadrado, ya que no tiene ningún beneficio de costo.Respuestas:
Básicamente queremos que las cosas se comporten con sensatez.
Considere el siguiente problema:
Me dan un grupo de rectángulos y quiero aumentar su área en un 10%. Entonces, lo que hago es establecer la longitud del rectángulo a 1.1 veces lo que era antes.
Ahora, en este caso, todos mis rectángulos ahora tienen su longitud aumentada en un 10%, lo que aumentará su área en un 10%. Desafortunadamente, alguien realmente me pasó una mezcla de cuadrados y rectángulos, y cuando cambió la longitud del rectángulo, también lo hizo el ancho.
Mis pruebas unitarias pasan porque escribí todas mis pruebas unitarias para usar una colección de rectángulos. Ahora he introducido un error sutil en mi aplicación que puede pasar desapercibido durante meses.
Peor aún, Jim de contabilidad ve mi método y escribe otro código que usa el hecho de que si pasa cuadrados a mi método, obtiene un aumento de tamaño del 21%. Jim es feliz y nadie es más sabio.
Jim es promovido por su excelente trabajo a una división diferente. Alfred se une a la empresa como junior. En su primer informe de error, Jill de Advertising ha informado que pasar cuadrados a este método resulta en un aumento del 21% y quiere que se solucione el error. Alfred ve que los cuadrados y los rectángulos se usan en todas partes del código y se da cuenta de que es imposible romper la cadena de herencia. Tampoco tiene acceso al código fuente de Contabilidad. Entonces Alfred corrige el error así:
Alfred está contento con sus habilidades de piratería súper y Jill firma que el error está solucionado.
El mes próximo, a nadie se le paga porque la Contabilidad dependía de poder pasar cuadrados al
IncreaseRectangleSizeByTenPercent
método y obtener un aumento en el área del 21%. Toda la empresa pasa al modo "corrección de errores de prioridad 1" para rastrear el origen del problema. Ellos rastrean el problema hasta la solución de Alfred. Saben que deben mantener contentos tanto a la contabilidad como a la publicidad. Entonces arreglan el problema identificando al usuario con la llamada al método de la siguiente manera:Y así sucesivamente y así sucesivamente.
Esta anécdota se basa en situaciones del mundo real que enfrentan los programadores a diario. Las violaciones del principio de sustitución de Liskov pueden introducir errores muy sutiles que solo se detectan años después de que se escriben, momento en el que corregir la violación romperá un montón de cosas y no arreglarlo enojará a su mayor cliente.
Hay dos formas realistas de solucionar este problema.
La primera forma es hacer que Rectángulo sea inmutable. Si el usuario de Rectángulo no puede cambiar las propiedades Longitud y Ancho, este problema desaparece. Si desea un rectángulo con una longitud y ancho diferentes, cree uno nuevo. Los cuadrados pueden heredar de rectángulos felizmente.
La segunda forma es romper la cadena de herencia entre cuadrados y rectángulos. Si un cuadrado se define como tener una sola
SideLength
propiedad y rectángulos tienen unaLength
yWidth
la propiedad y no hay herencia, que es imposible de romper accidentalmente cosas al esperar un rectángulo y conseguir una plaza. En términos de C #, podríaseal
su clase de rectángulo, lo que garantiza que todos los Rectángulos que obtenga sean en realidad Rectángulos.En este caso, me gusta la forma de "objetos inmutables" de solucionar el problema. La identidad de un rectángulo es su longitud y ancho. Tiene sentido que cuando quiera cambiar la identidad de un objeto, lo que realmente quiere es un objeto nuevo . Si pierde un antiguo cliente y gana un nuevo cliente, no cambia el
Customer.Id
campo del antiguo cliente al nuevo, crea uno nuevoCustomer
.Las violaciones del principio de sustitución de Liskov son comunes en el mundo real, principalmente porque gran parte del código está escrito por personas incompetentes / bajo presión de tiempo / no les importa / cometen errores. Puede y causa algunos problemas muy desagradables. En la mayoría de los casos, desea favorecer la composición sobre la herencia .
fuente
Si todos sus objetos son inmutables, no hay problema. Cada cuadrado es también un rectángulo. Todas las propiedades de un rectángulo también son propiedades de un cuadrado.
El problema comienza cuando agrega la capacidad de modificar los objetos. O realmente, cuando comienzas a pasar argumentos al objeto, no solo leyendo captadores de propiedades.
Hay modificaciones que puede hacer en un Rectángulo que mantienen todos los invariantes de su clase Rectángulo, pero no todos los invariantes Cuadrados, como cambiar el ancho o la altura. De repente, el comportamiento de un rectángulo no es solo sus propiedades, sino también sus posibles modificaciones. No es solo lo que obtienes del Rectángulo, también es lo que puedes poner .
Si su Rectángulo tiene un método
setWidth
documentado que cambia el ancho y no modifica la altura, Square no puede tener un método compatible. Si cambia el ancho y no la altura, el resultado ya no es un cuadrado válido. Si elige modificar tanto el ancho como la altura del Cuadrado cuando lo usasetWidth
, no está implementando la especificación de RectángulosetWidth
. Simplemente no puedes ganar.Cuando observa lo que puede "poner" en un Rectángulo y un Cuadrado, qué mensajes puede enviarles, es probable que encuentre cualquier mensaje que pueda enviar válidamente a un Cuadrado, también puede enviarlo a un Rectángulo.
Es una cuestión de covarianza vs. contravarianza.
Los métodos de una subclase adecuada, una en la que se pueden usar instancias en todos los casos en que se espera la superclase, requieren que cada método:
Entonces, volviendo a Rectángulo y Cuadrado: si Cuadrado puede ser una subclase de Rectángulo depende completamente de los métodos que tenga Rectángulo.
Si Rectangle tiene setters individuales para ancho y alto, Square no será una buena subclase.
Del mismo modo, si hace que algunos métodos sean covariantes en los argumentos, como tener
compareTo(Rectangle)
un Rectángulo ycompareTo(Square)
un Cuadrado, tendrá un problema al usar un Cuadrado como un Rectángulo.Si diseñas tu Square y Rectangle para que sea compatible, probablemente funcionará, pero deberían desarrollarse juntos, o apuesto a que no funcionará.
fuente
Hay muchas buenas respuestas aquí; La respuesta de Stephen en particular hace un buen trabajo al ilustrar por qué las violaciones del principio de sustitución conducen a conflictos en el mundo real entre equipos.
Pensé que podría hablar brevemente sobre el problema específico de los rectángulos y cuadrados, en lugar de usarlo como una metáfora de otras violaciones del LSP.
Hay un problema adicional con el cuadrado-es-un-tipo-especial-de-rectángulo que rara vez se menciona, y es: ¿por qué nos detenemos con cuadrados y rectángulos ? Si estamos dispuestos a decir que un cuadrado es un tipo especial de rectángulo, entonces seguramente también deberíamos estar dispuestos a decir:
¿Qué demonios deberían estar todas las relaciones aquí? Los lenguajes basados en herencia de clase como C # o Java no fueron diseñados para representar este tipo de relaciones complejas con múltiples tipos diferentes de restricciones. Es mejor evitar la pregunta por completo al no tratar de representar todas estas cosas como clases con relaciones de subtipo.
fuente
IShape
tipo que incluye un cuadro delimitador, y se puede dibujar, escalar y serializar, y tener unIPolygon
subtipo con un método para informar el número de vértices y un método para devolver unIEnumerable<Point>
. Entonces se podría tener unIQuadrilateral
subtipo que deriva deIPolygon
,IRhombus
yIRectangle
, deriva de eso, yISquare
deriva deIRhombus
yIRectangle
. La mutabilidad arrojaría todo por la ventana, y la herencia múltiple no funciona con clases, pero creo que está bien con interfaces inmutables.IRhombus
garantiza que todo loPoint
devuelto por loEnumerable<Point>
definido porIPolygon
corresponda a bordes de igual longitud? Debido a que la implementación de laIRhombus
interfaz por sí sola no garantiza que un objeto concreto sea un rombo, la herencia no puede ser la respuesta.Desde una perspectiva matemática, un cuadrado es un rectángulo. Si un matemático modifica el cuadrado para que ya no se adhiera al contrato del cuadrado, cambia a un rectángulo.
Pero en el diseño OO, esto es un problema. Un objeto es lo que es, y esto incluye comportamientos y estado. Si sostengo un objeto cuadrado, pero alguien más lo modifica para que sea un rectángulo, eso viola el contrato del cuadrado sin culpa mía. Esto hace que sucedan todo tipo de cosas malas.
El factor clave aquí es la mutabilidad . ¿Puede cambiar una forma una vez que se construye?
Mutable: si se permite que las formas cambien una vez construidas, un cuadrado no puede tener una relación is-a con un rectángulo. El contrato de un rectángulo incluye la restricción de que los lados opuestos deben tener la misma longitud, pero los lados adyacentes no necesitan serlo. El cuadrado debe tener cuatro lados iguales. Modificar un cuadrado a través de una interfaz rectangular puede violar el contrato del cuadrado.
Inmutable: si las formas no pueden cambiar una vez construido, entonces un objeto cuadrado también debe cumplir siempre el contrato de rectángulo. Un cuadrado puede tener una relación is-a con rectángulo.
En ambos casos, es posible pedirle a un cuadrado que produzca una nueva forma basada en su estado con uno o más cambios. Por ejemplo, uno podría decir "crear un nuevo rectángulo basado en este cuadrado, excepto que los lados opuestos A y C son dos veces más largos". Como se está construyendo un nuevo objeto, el cuadrado original continúa adhiriéndose a sus contratos.
fuente
This is one of those cases where the real world is not able to be modeled in a computer 100%
. ¿Porque? Todavía podemos tener un modelo funcional de un cuadrado y un rectángulo. La única consecuencia es que tenemos que buscar una construcción más simple para abstraer sobre esos dos objetos.Porque eso es parte de lo que significa ser un subtipo (ver también: principio de sustitución de Liskov). Puede hacer, necesita poder hacer esto:
Realmente haces esto todo el tiempo (a veces incluso más implícitamente) cuando usas OOP.
Porque no puedes anularlas sensiblemente
Square
. Porque un cuadrado no puede "redimensionarse como cualquier rectángulo". Cuando cambia la altura de un rectángulo, el ancho permanece igual. Pero cuando cambia la altura de un cuadrado, el ancho debe cambiar en consecuencia. El problema no es solo ser redimensionable, sino también redimensionable en ambas dimensiones de forma independiente.fuente
Rect r = s;
línea, solo puededoSomethingWith(s)
y el tiempo de ejecución usará cualquier llamadas
para resolver cualquierSquare
método virtual .setWidth
ysetHeight
cambie tanto el ancho como la altura.Lo que estás describiendo va en contra de lo que se llama el principio de sustitución de Liskov . La idea básica del LSP es que siempre que use una instancia de una clase en particular, siempre debería poder intercambiar en una instancia de cualquier subclase de esa clase, sin introducir errores.
El problema del rectángulo cuadrado no es realmente una muy buena forma de presentar a Liskov. Trata de explicar un principio amplio utilizando un ejemplo que en realidad es bastante sutil y está en conflicto con una de las definiciones intuitivas más comunes en todas las matemáticas. Algunos lo llaman el problema de Ellipse-Circle por esa razón, pero es solo un poco mejor en lo que respecta a esto. Un mejor enfoque es retroceder un poco, utilizando lo que yo llamo el problema del paralelogramo-rectángulo. Esto hace que las cosas sean mucho más fáciles de entender.
Un paralelogramo es un cuadrilátero con dos pares de lados paralelos. También tiene dos pares de ángulos congruentes. No es difícil imaginar un objeto de paralelogramo a lo largo de estas líneas:
Una forma común de pensar en un rectángulo es como un paralelogramo con ángulos rectos. A primera vista, esto podría parecer que Rectangle sea un buen candidato para heredar de Parallelogram , de modo que pueda reutilizar todo ese delicioso código. Sin embargo:
¿Por qué estas dos funciones introducen errores en Rectángulo? El problema es que no puedes cambiar los ángulos en un rectángulo : se definen como siempre de 90 grados, por lo que esta interfaz no funciona para Rectángulo heredando de Paralelogramo. Si cambio un Rectángulo a un código que espera un Paralelogramo, y ese código intenta cambiar el ángulo, es muy probable que haya errores. Tomamos algo que se podía escribir en la subclase y lo hicimos de solo lectura, y eso es una violación de Liskov.
Ahora, ¿cómo se aplica esto a cuadrados y rectángulos?
Cuando decimos que puede establecer un valor, generalmente queremos decir algo un poco más fuerte que simplemente poder escribir un valor en él. Implicamos un cierto grado de exclusividad: si establece un valor, salvo algunas circunstancias extraordinarias, se mantendrá en ese valor hasta que lo establezca nuevamente. Hay muchos usos para los valores en los que se puede escribir pero que no permanecen establecidos, pero también hay muchos casos que dependen de que un valor permanezca donde está una vez que lo establece. Y ahí es donde nos encontramos con otro problema.
Nuestra clase Square heredó errores de Rectangle, pero tiene algunos nuevos. El problema con setSideA y setSideB es que ninguno de estos ya es realmente configurable: aún puede escribir un valor en cualquiera de ellos, pero cambiará por debajo de usted si se escribe en el otro. Si cambio esto por un paralelogramo en el código que depende de poder establecer lados independientemente uno del otro, se volverá loco.
Ese es el problema, y es por eso que hay un problema con el uso de Rectangle-Square como una introducción a Liskov. Rectangle-Square depende de la diferencia entre poder escribir en algo y poder configurarlo , y esa es una diferencia mucho más sutil que poder configurar algo en lugar de que sea de solo lectura. Rectangle-Square todavía tiene valor como ejemplo, porque documenta un problema bastante común que debe ser observado, pero no debe usarse como un ejemplo introductorio . Deje que el alumno se base primero en lo básico y luego arroje algo más duro a ellos.
fuente
El subtipo se trata de comportamiento.
Para que el tipo
B
sea un subtipo de tipoA
, debe admitir todas las operaciones que el tipoA
admite con la misma semántica (charla elegante para "comportamiento"). Usando el argumento de que cada B es un A qué no funciona - la compatibilidad comportamiento tiene la última palabra. La mayoría de las veces "B es una especie de A" se superpone con "B se comporta como A", pero no siempre .Un ejemplo:
Considere el conjunto de números reales. En cualquier idioma, podemos esperar que apoyan las operaciones
+
,-
,*
, y/
. Ahora considere el conjunto de enteros positivos ({1, 2, 3, ...}). Claramente, cada entero positivo también es un número real. ¿Pero es el tipo de enteros positivos un subtipo del tipo de números reales? Veamos las cuatro operaciones y veamos si los enteros positivos se comportan de la misma manera que los números reales:+
: Podemos agregar enteros positivos sin problemas.-
: No todas las restas de enteros positivos resultan en enteros positivos. Por ej3 - 5
.*
: Podemos multiplicar enteros positivos sin problemas./
: No siempre podemos dividir enteros positivos y obtener un entero positivo. Por ej5 / 3
.Entonces, a pesar de que los enteros positivos son un subconjunto de números reales, no son un subtipo. Se puede hacer un argumento similar para enteros de tamaño finito. Claramente, cada entero de 32 bits también es un entero de 64 bits, pero
32_BIT_MAX + 1
le dará diferentes resultados para cada tipo. Entonces, si te di algún programa y cambiaste el tipo de cada variable entera de 32 bits a enteros de 64 bits, hay una buena probabilidad de que el programa se comporte de manera diferente (lo que casi siempre significa mal ).Por supuesto, podría definir
+
entradas de 32 bits para que el resultado sea un entero de 64 bits, pero ahora tendrá que reservar 64 bits de espacio cada vez que agregue dos números de 32 bits. Eso puede o no ser aceptable para usted dependiendo de sus necesidades de memoria.¿Por qué importa esto?
Es importante que los programas sean correctos. Podría decirse que es la propiedad más importante para un programa. Si un programa es correcto para algún tipo
A
, la única forma de garantizar que el programa continuará siendo correcto para algún subtipoB
es si seB
comporta comoA
en todos los sentidos.Entonces tiene el tipo de
Rectangles
, cuya especificación dice que sus lados se pueden cambiar de forma independiente. Usted escribió algunos programas que usanRectangles
y asumen que la implementación sigue la especificación. Luego introdujo un subtipo llamadoSquare
cuyos lados no se pueden cambiar de tamaño de forma independiente. Como resultado, la mayoría de los programas que redimensionan rectángulos ahora estarán equivocados.fuente
Primero lo primero, pregúntese por qué cree que un cuadrado es un rectángulo.
Por supuesto, la mayoría de la gente aprendió eso en la escuela primaria, y parecería obvio. Un rectángulo es una forma de 4 lados con ángulos de 90 grados, y un cuadrado cumple con todas esas propiedades. Entonces, ¿no es un cuadrado un rectángulo?
Sin embargo, la cuestión es que todo depende de cuál sea su criterio inicial para agrupar objetos, qué contexto está viendo estos objetos. En geometría las formas se clasifican en función de las propiedades de sus puntos, líneas y ángeles.
Entonces, incluso antes de decir "un cuadrado es un tipo de rectángulo", primero tiene que preguntarse si esto se basa en criterios que me interesan .
En la gran mayoría de los casos, no va a ser lo que te importa en absoluto. La mayoría de los sistemas que modelan formas, como GUI, gráficos y videojuegos, no se ocupan principalmente de la agrupación geométrica de un objeto, sino de su comportamiento. ¿Alguna vez has trabajado en un sistema que importaba que un cuadrado fuera un tipo de rectángulo en sentido geométrico? ¿Qué te daría eso, sabiendo que tiene 4 lados y ángulos de 90 grados?
No está modelando un sistema estático, está modelando un sistema dinámico donde las cosas van a suceder (las formas se van a crear, destruir, alterar, dibujar, etc.). En este contexto, le importa el comportamiento compartido entre los objetos, porque su principal preocupación es lo que puede hacer con una forma, qué reglas se deben mantener para tener un sistema coherente.
En este contexto, un cuadrado definitivamente no es un rectángulo , porque las reglas que gobiernan cómo se puede alterar el cuadrado no son las mismas que el rectángulo. Entonces no son el mismo tipo de cosas.
En cuyo caso, no los modele como tal. ¿Por que lo harias? No gana nada más que una restricción innecesaria.
Si lo hace, aunque prácticamente está indicando en código que no son lo mismo. Su código estaría diciendo que un cuadrado se comporta de esta manera y un rectángulo se comporta de esa manera, pero siguen siendo los mismos.
Claramente no son lo mismo en el contexto que le interesa porque acaba de definir dos comportamientos diferentes. Entonces, ¿por qué fingir que son iguales si solo son similares en un contexto que no te importa?
Esto resalta un problema importante cuando los desarrolladores llegan a un dominio que desean modelar. Es muy importante aclarar en qué contexto está interesado antes de comenzar a pensar en los objetos del dominio. ¿Qué aspecto le interesa? Hace miles de años, los griegos se preocuparon por las propiedades compartidas de las líneas y los ángeles de las formas, y las agruparon en función de ellas. Eso no significa que se vea obligado a continuar esa agrupación si no es lo que le interesa (que en el 99% del tiempo no le interesará modelar en software).
Muchas de las respuestas a esta pregunta se centran en subtipar sobre el comportamiento de agrupación porque les da las reglas .
Pero es tan importante comprender que no está haciendo esto solo para seguir las reglas. Estás haciendo esto porque en la gran mayoría de los casos esto es lo que realmente te importa también. No te importa si un cuadrado y un rectángulo comparten los mismos ángeles internos. Te importa lo que pueden hacer mientras siguen siendo cuadrados y rectángulos. Te importa el comportamiento de los objetos porque estás modelando un sistema que se centra en cambiar el sistema en función de las reglas del comportamiento de los objetos.
fuente
Rectangle
solo se usan para representar valores , entonces es posible que una claseSquare
heredeRectangle
y cumpla completamente su contrato. Desafortunadamente, muchos lenguajes no distinguen entre variables que encapsulan valores y aquellas que identifican entidades.Square
tipo inmutable que herede de unRectnagle
tipo inmutable podría ser útil si hubiera algún tipo de operaciones que solo pudieran realizarse sobre cuadrados. Como un ejemplo realista del concepto, considere unReadableMatrix
tipo [tipo base, una matriz rectangular que podría almacenarse de varias maneras, incluso escasamente], y unComputeDeterminant
método. Puede tener sentidoComputeDeterminant
trabajar solo con unReadableSquareMatrix
tipo derivadoReadableMatrix
, lo que consideraría un ejemplo deSquare
derivación de aRectangle
.El problema radica en pensar que si las cosas están relacionadas de alguna manera en la realidad, deben estar relacionadas exactamente de la misma manera después del modelado.
Lo más importante en el modelado es identificar los atributos comunes y los comportamientos comunes, definirlos en la clase básica y agregar atributos adicionales en las clases secundarias.
El problema con su ejemplo es que es completamente abstracto. Mientras nadie sepa, para qué planea usar esas clases, es difícil adivinar qué diseño debe hacer. Pero si realmente desea tener solo altura, ancho y cambio de tamaño, sería más lógico:
width
parámetro yresize(double factor)
redimensionando el ancho por el factor dadoheight
y anula suresize
función, que llamasuper.resize
y luego cambia el tamaño de la altura por el factor dadoDesde el punto de vista de la programación, no hay nada en Square que Rectangle no tenga. No tiene sentido hacer un cuadrado como la subclase de Rectángulo.
fuente
Porque por LSP, la creación de una relación de herencia entre los dos y la anulación
setWidth
ysetHeight
para garantizar que el cuadrado tenga ambas cosas, introduce un comportamiento confuso y no intuitivo. Digamos que tenemos un código:Pero si el método
createRectangle
regresóSquare
, porque es posible gracias a laSquare
herencia deRectange
. Entonces las expectativas se rompen. Aquí, con este código, esperamos que el ajuste de ancho o alto solo cause un cambio en ancho o alto respectivamente. El objetivo de OOP es que cuando trabajas con la superclase, no tienes conocimiento de ninguna subclase debajo de ella. Y si la subclase cambia el comportamiento para que vaya en contra de las expectativas que tenemos sobre la superclase, entonces hay una alta probabilidad de que ocurran errores. Y ese tipo de errores son difíciles de depurar y corregir.Una de las ideas principales sobre OOP es que es el comportamiento, no los datos que se heredan (que también es una de las principales ideas erróneas de la OMI). Y si nos fijamos en el cuadrado y el rectángulo, no tienen un comportamiento que podamos relacionar en relación de herencia.
fuente
Lo que dice LSP es que cualquier cosa que herede
Rectangle
debe ser aRectangle
. Es decir, debe hacer lo que sea queRectangle
haga.Probablemente la documentación para
Rectangle
está escrita para decir que el comportamiento de unRectangle
nombrador
es el siguiente:Si su Square no tiene el mismo comportamiento, entonces no se comporta como a
Rectangle
. Entonces LSP dice que no debe heredar deRectangle
. El lenguaje no puede hacer cumplir esta regla, porque no puede evitar que hagas algo mal en una anulación de método, pero eso no significa "está bien porque el lenguaje me permite anular los métodos" es un argumento convincente para hacerlo.Ahora, sería posible escribir la documentación de
Rectangle
tal manera que no implique que el código anterior imprima 10, en cuyo caso tal vezSquare
podría ser unRectangle
. Es posible que vea documentación que diga algo como "esto hace X. Además, la implementación en esta clase hace Y". Si es así, tiene un buen caso para extraer una interfaz de la clase y distinguir entre lo que garantiza la interfaz y lo que la clase garantiza además de eso. Pero cuando la gente dice "un cuadrado mutable no es un rectángulo mutable, mientras que un cuadrado inmutable es un rectángulo inmutable", básicamente asumen que lo anterior es de hecho parte de la definición razonable de un rectángulo mutable.fuente
Los subtipos y, por extensión, la programación OO, a menudo se basan en el Principio de sustitución de Liskov, que cualquier valor de tipo A se puede utilizar donde se requiere un B, si A <= B. Esto es más o menos un axioma en la arquitectura OO, es decir. se supone que todas las subclases tendrán esta propiedad (y si no, los subtipos tienen errores y deben corregirse).
¡Sin embargo, resulta que este principio es poco realista / no representativo de la mayoría del código, o de hecho imposible de satisfacer (en casos no triviales)! Este problema, conocido como el problema del rectángulo cuadrado o el problema de la elipse circular ( http://en.wikipedia.org/wiki/Circle-ellipse_problem ) es un famoso ejemplo de lo difícil que es cumplirlo.
Tenga en cuenta que podríamos implementar Cuadrados y Rectángulos más y más equivalentes a la observación, pero solo descartando cada vez más funcionalidades hasta que la distinción sea inútil.
Como ejemplo, vea http://okmij.org/ftp/Computation/Subtyping/
fuente