1 / 13
ago. 2017

por definición, todas las clases que heredan de una misma clase padre son intercambiables

Ojo con esa afirmación que no es cierta. Por poner el ejemplo más evidente, si yo heredo de una clase "calculadora" y hago un override del método "sumar" haciendo que la implementación reste, estás violando Liskov.

Es más, si lanzas excepciones distintas desde dos implementaciones del mismo método, también estás violando Liskov.

A parte de lo que te ha comentado @msanjuan incluso en lenguajes dinámicos puedes encontrarte sobreescrituras de métodos dónde una clase devuelva un string en un método y otra un integer en el mismo método sobreescrito (no se debe hacer, pero el lenguaje lo permite).

Además a mi me gusta tener en cuenta el contexto, es decir, imagina 2 clases con el mismo método, pero contextualmente no serían intercambiables. Hay polimorfismo porque en teoría son intercambiables, pero a la hora de usar las clases no lo puedes aplicar.

Imagina que tengo B y C que extienden una clase A. Tienen el mismo método exec() sin tipo de retorno, por lo que en teoría serían intercambiables. Pero a la hora de usarlos se hace en sitios diferentes en plan:

  • En la clase 1 se hace un new B y se ejecuta exec().
  • En la clase 2 se hace un new C y se ejecuta exec().

Pero contextualmente no tiene sentido intercambiarlos, por lo que B y C no deberían ser objetos del mismo tipo. Diferentes contextos.

De esto he sufrido un caso heredado en el que 5 clases heredan de una común bastante grande, pero luego se usa cada una en su sitio distinto y en distinto contexto. Aquí se ha llegado a que incluso métodos y atributos protected de la clase padre no se usen en todas las clases hijas, sólo en algunas. Las 5 clases no son del mismo tipo, y trabajar ese código es bastante confuso.

En cambio B y C del ejemplo de arriba sí podrían componerse de un objeto A que utilizarían cada uno para sus fines. Aquí prima la composición y la herencia no tiene sentido porque nunca vas a generar B y C de forma dinámica en tiempo de ejecución.

OJO: Esto puede tener sus excepciones y es matizable, a veces sí podría tener sentido que fuesen del mismo tipo, pero habría que pensarlo mucho para ver si se descarta composición.

¿Entonces cuando este principio habla de "sustitución" no se refiere a que el lenguaje/compilador acepte el intercambio de clases si no que conceptualmente tenga sentido?

¿si tuviéramos una suite de tests que verificaran el comportamiento de una clase padre... sustituyendo esa clase padre por cualquier hija deberían seguir pasando los tests no? En el caso de la calculadora que comenta @msanjuan no sería así.

Exacto. Ten en cuenta que, como comenta @altorsaz, podríamos estar hablando de lenguajes dinámicos en los que el compilador aceptaría esa ruptura del contrato sin quejarse.

La L de SOLID no sólo se refiere a que los interfaces sean compatibles en firma (contrato), también deben serlo en comportamiento. En realidad, deberíamos partir de la base de que un contrato no sólo se refiere a la firma que el compilador pueda estar verificando, el contrato debe extenderse al comportamiento.

El principio de Liskov funciona muy muy bien cuando entiendes que loq ue debes de trabajar es con interfaces y nunca con implementaciones.

Esto quiere decir que siempre debes recibir y retornar como parametros interfaces para que el consumidor no necesite "especificar" un tipo.

Liskov no entra en si usas interfaces o implementaciones. Al final habrá una o varias implementaciones. Liskov entra en si esas implementaciones son sustituíbles o no.

Puedes usar interfaces para todo y romper Liskov sin problemas...

Esto es lo que opina Yegor Bugayenko1 sobre este principio:

Este es el principio más inocente de los cinco que forman SOLID. Hablando claro, dice que si tu método espera recibir una Collection y le llega una ArrayList, debe funcionar.

También esto es conocido como subtipo y es una parte fundamental de cualquier lenguaje orientado a objetos. ¿Por qué necesitamos para esto crear un principio y seguirlo? ¿Acaso es posible crear un software orientado a objetos sin subtipos? Si esto es un principio, también lo son las "variables" o las "llamadas a métodos".

Sinceramente, sospecho que este principio se añadió a SOLID más que nada para cubrir el hueco entre SO e ID.

Original en inglés

This one is the most innocent part in the SOLID pentad. In simple words, it states that if your method expects a Collection, an ArrayList wil l work.

It is also known as subtyping and is the foundational component of any object-oriented language. Why do we need to call it a principle and "follow" it? Is it at all possible to create any object-oriented software without subtyping? If this one is a principle, let's add "variables" and "method calling " here too.

Honestly, I suspect that this principle was added to SOLID mostly in order to somehow fill the gap between "SO" and "ID."

Forma parte de un artículo criticando los principios SOLID8. Me sorprende que una persona con tanto bagaje tenga una visión tan aparentemente limitada del principio de Liskov. Aunque Bugayenko es un provocador y uno nunca sabe cuando habla en serio y cuando solo busca llamar la atención...

Sí, es un provocador en toda regla y hay veces que no sabes si habla en serio o no. Por ejemplo este post8 nunca supe si era serio o en modo parodia.

Aunque la crítica a los principios SOLID creo que la hace en serio (o no).

9 días después

A mí me paso lo mismo, lees y no sabes si va en serio o está de coña.

Por cierto, ¿alguien ha leído alguno de sus libros? He visto que solamente están en papel y nose si pillarme alguno a ver que tal.

Yo no los he leído, pero por lo que he visto son en su mayor parte los artículos de su blog extendidos.

Aquí hay una crítica de hace un día de uno de ellos: