En mi último post hablé del principo Open/Closed, este principio es la base para contruir código que es mantenible y reusable. Este principio sotiene que un código bien diseñado puede ser extendido sin modificación; que la nuevas características en un programa bien diseñado son implementadas adicionando nuevo código en lugar de cambiar el código existente, código que ya funciona.
Los conceptos de POO detrás de este principio son Abstracción y Polimorfismo, en la mayoría de lenguajes orientados a objetos estos dos conceptos se apoyan en la Herencia, una de las características claves de POO.
Es mediante la herencia que podemos crear clases derivadas que se ajusten a las interfaces abstractas polimórficas definidas por las funciones virtuales puras en las clases de base abstracta.
¿Cuáles son las características de las jerarquías mejor herencia?
¿Cuáles son las trampas que nos llevan a crear jerarquías que no se ajusten al principio abierto-cerrado?
Estas son las preguntas que el principio Liskov resuelve y que este post abordará.
Herencia
Uno de los pilares de la POO es la herencia, esta permite que a partir de una clase base se pueda definir unos comportamientos generales, pero a su vez que las clases hijas definan unos comportamientos particulares, lo que nos da reusabilidad y polimorfismo.
Pero la herencia mal utilizada hace que métodos que reciban como parámetro a clases base tengan que hacer una distinción entre las clases hijas de estas para poder funcionar adecuadamente correctamente.
Dado lo anterior se destaca la importancia del principio Liskov y se hace evidente cuando se tiene en cuenta las consecuencias de violarlo. Si hay una función que no sea conforme al principio Liskow, luego de que la función utiliza un puntero o una referencia a una clase base, sino que debe saber sobre todos los derivados de esa clase base. Tal función, viola el principio Open/Closed, ya que se debe modificar cada vez que un nuevo derivado de la clase base se crea.
Si te comportas diferente a mi, no puedes ser mi heredero.
Esto podría ser la frase de un padre demasiado estricto, pero es mas o menos de lo que trata el principio Liskov, un ejemplo es el clásico problema del Rectangulo y el Cudrado, el Cuadrado matemáticamente es una especie de Reactangulo, pero desde el punto de vista de programación realmente un objeto Cuadrado tiene un comportamiento diferente al de un objeto Rectángulo, veamos que pasa cuando asumimos que un objeto Cuadrado puede heredar de un objeto Rectángulo:
public class Rectangle { int _width; public virtual int Width { get { return _width; } set { _width = value; } } int _height; public virtual int Height { get { return _height; } set { _height = value; } } public int Area { get { return _height * _width; } } } public class Square:Rectangle { public override int Height { get { return base.Height; } set { base.Height = value; base.Width = value; } } public override int Width { get { return base.Width; } set { base.Width = value; base.Height = value; } } } class Program { static void Main(string[] args) { try { Rectangle rect = new Rectangle(); PruebaUnitariaSencilla(rect); Console.WriteLine("Paso la prueba no 1."); } catch (Exception ex) { Console.WriteLine("No paso la prueba no 1. " + ex.Message); } try { Square squ = new Square(); PruebaUnitariaSencilla(squ); Console.WriteLine("Paso la prueba no 2."); } catch (Exception ex) { Console.WriteLine("No paso la prueba no 2. " + ex.Message); } Console.ReadLine(); } static void PruebaUnitariaSencilla(Rectangle rectangle) { rectangle.Height = 5; rectangle.Width = 4; if (rectangle.Area == 20) { return; } throw new Exception("El valor esperado era 20."); } }
El método PruebaUnitariaSencilla aunque no está preguntando si el parámetro rectangle es un objeto de tipo Rectangle o de tipo Square viola el principio Liskov, debido a que este se quiebra cuando le pasa un objeto de tipo Square, pero en realidad este método no es culpable de esto, ya que realmente la culpa es de que un objeto Square no debería heredar de Rectangle dado que sus comportamientos en realidad son diferentes.
Una solución inconveniente
El problema es que cuando detectamos errores como el anterior, lo que hacemos es violar el principio Open/Closed ya que lo que hacemos es hacer que el método no se quiebre falle de la siguiente manera:
static void PruebaUnitariaSencilla(Rectangle rectangle) { if (typeof(Rectangle) == rectangle.GetType()) { rectangle.Height = 5; rectangle.Width = 4; if (rectangle.Area == 20) { return; } throw new Exception("El valor esperado era 20."); } else if (typeof(Square) == rectangle.GetType()) { rectangle.Height = 5; if (rectangle.Area == 25) { return; } throw new Exception("El valor esperado era 25."); } }
El problema con la solución anterior es que si se crea un nuevo tipo que herede del objeto Rectangle se debe modificar el método PruebaUnitariaSencilla.
Pero entonces que hacer?
En realidad todo empezó mal por asumir que un objeto Square podía heredar del objeto Rectangle, es por esta razón que al momento de utilizar la herencia debemos de realmente evaluar los comportamientos de los objetos que son base y de los objetos que pueden llegar a ser hijos.
Fuentes:
http://www.objectmentor.com/resources/articles/lsp.pdf
http://agenda.ictp.trieste.it/agenda_links/smr1335/OOP/node25.html
http://en.wikipedia.org/wiki/Circle-ellipse_problem
http://www.oodesign.com/liskov-s-substitution-principle.html
http://en.wikipedia.org/wiki/Liskov_substitution_principle
En realidad todo empezó mal por asumir que un objeto Square podía heredar del objeto Rectangle, es por esta razón que al momento de utilizar la herencia debemos de realmente evaluar los comportamientos de los objetos que son base y de los objetos que pueden llegar a ser hijos.
Fuentes:
http://www.objectmentor.com/resources/articles/lsp.pdf
http://agenda.ictp.trieste.it/agenda_links/smr1335/OOP/node25.html
http://en.wikipedia.org/wiki/Circle-ellipse_problem
http://www.oodesign.com/liskov-s-substitution-principle.html
http://en.wikipedia.org/wiki/Liskov_substitution_principle
Happy coding.
The Clean Coder, Because small things matter.
No hay comentarios:
Publicar un comentario