Este principio, desarrollado principalmente por Barbara Liskov, reza lo siguiente:
Debe ser posible utilizar cualquier objeto instancia de una subclase en lugar de cualquier objeto instancia de su superclase sin que la semántica del programa escrito en los términos de la superclase se vea afectado
Fácil, ¿verdad?. Bueno, creo que será mucho más sencillo explicarlo con un ejemplo. Casi todos los ejemplos referentes a Liskov Substitution se centran en un rectángulo y un cuadrado. Probemos algo diferente.
Supongamos que tenemos una super clase Pájaro, con sus métodos de comer y volar. Por otra parte tenemos un par de clases más, Pingüino y Cuervo, siendo ambas hijas de nuestra clase pájaro:
class Bird def eat puts "¡Wow, estoy comiendo!" end def fly puts "¡Wow, estoy volando!" end end class Crow < Bird def initialize(color = "black") @color = color puts "Hola, soy un cuervo de color #{@color}" end end class Penguin < Bird def initialize(color = "black") @color = color puts "Hola, soy un pingüino de color #{@color}" end def fly raise "No puedo volar, snifff" end end
Liskov Substitution sostiene que si conocemos como funciona la clase Pájaro y sus métodos debemos esperar que tanto Pingüino como Cuervo se comporten de la misma forma. Por tanto, alguien que vaya a trabajar con estas clases podrá crear sendos objetos Pingüino y Cuervo y pretender que coman y vuelen:
my_bird = Crow.new puts my_bird.eat puts my_bird.fly other_bird = Penguin.new puts other_bird.eat puts other_bird.fly # Y nos encontramos con un error
Cuando esperamos encontrar un bonito vuelo de nuestro pingüino nos encontramos con un error similar al siguiente:
irb(main):124:0> puts other_bird.fly RuntimeError: No puedo volar, snifff from (irb):114:in `fly' from (irb):124 from ~/.rbenv/versions/2.1.3/bin/irb:11:in `<main>'
La respuesta es simple: un pingüino no es un pájaro. No, no me he vuelto loco. En este contexto un pingüino es una porción de código, o lo que es lo mismo, una representación imaginaria de algo real. Si tenemos en cuenta esto podemos comprender que las relaciones de la vida real pueden no servirnos al realizar sus representaciones programadas. Citando a Bob Martin la explicación más sencilla para entender esto es la siguiente:
Imaginemos que dos personas quieren divorciarse. Cada una de ellas contratará a un abogado que se hará cargo de su representación. Es muy poco probable que esos dos abogados también se estén divorciando porque las representaciones de las cosas no comparte las relaciones de las cosas que representan.
Como acostumbro a hacer, el ejemplo anteriormente realizado está escrito en Ruby, algo que hace que este principio pueda ser algo diferente en otros lenguajes orientados a objetos. Ruby es un lenguaje dinámico, lo que implica que hace uso del Duck Typing, un concepto que explicaré en otro artículo ampliamente pero que, en resumidas cuentas, quiere decir que la validez semántica de nuestro código viene definía por los métodos y propiedades que se utilizan en el mismo, y no por la herencia de clases o interfaces del mismo, como ocurriría en otros lenguajes (por ejemplo Java).
Cuando veo un ave que camina como un pato, nada como un pato y suena como un pato, a esa ave yo la llamo pato
El problema del Duck Typing y este principio es que podemos estar cometiendo una violación del mismo pero nuestro código será semánticamente correcto. Por tanto debemos fijarnos más en las respuestas que el código nos devuelve, para comprobar que sea lo que cabría esperar.
En nuestro ejemplo tenemos un pingüino que caminaba como un pingüino, comía como un pingüino, pero pretendía volar, ya que su clase padre permite volar. Para evitar que volara tuvimos que preparar una excepción que se lo impidiera, y esto es una clara violación del principio.
buenas prácticas, desarrollo, development, SOLID
Comments RSS Feed