¿Cómo verificar el principio de sustitución de Liskov en una jerarquía de herencia?

14

Inspirado por esta respuesta:

  

Principio de sustitución de Liskov   requiere   que

     
  • Las condiciones previas no se pueden reforzar en un subtipo.
  •   
  • Las condiciones posteriores no se pueden debilitar en un subtipo.
  •   
  • Las invariantes del supertipo se deben conservar en un subtipo.
  •   
  • Restricción de historial (la "regla de historial"). Se considera que los objetos son modificables solo a través de sus métodos (encapsulación). Ya que   los subtipos pueden introducir métodos que no están presentes en el supertipo,   La introducción de estos métodos puede permitir cambios de estado en el   Subtipo que no está permitido en el supertipo. La historia   La restricción lo prohíbe.
  •   

Esperaba que alguien publicara una jerarquía de clases que violara estos 4 puntos y cómo resolverlos en consecuencia.
Estoy buscando una explicación elaborada con fines educativos sobre cómo identificar cada uno de los 4 puntos en la jerarquía y la mejor manera de solucionarlo.

Nota:
Esperaba publicar una muestra de código para que la gente trabajara, pero la pregunta en sí misma es sobre cómo identificar las jerarquías defectuosas :)

    
pregunta Songo 17.10.2012 - 12:45

1 respuesta

16

Es mucho más simple de lo que dice la cita, por muy precisa que sea.

Cuando observa una jerarquía de herencia, imagine un método que recibe un objeto de la clase base. Ahora pregúntese, ¿existen suposiciones que alguien que edite este método podría hacer que no sería válido para esa clase?

Por ejemplo ( originalmente visto en el sitio de Uncle Bob ):

public class Square : Rectangle
{
    public Square(double width) : base(width, width)
    {
    }

    public override double Width
    {
        set
        {
            base.Width = value;
            base.Height = value;
        }
        get
        {
            return base.Width;
        }
    }

    public override double Height
    {
        set
        {
            base.Width = value;
            base.Height = value;
        }
        get
        {
            return base.Height;
        }
    }
}

Parece bastante justo, ¿verdad? He creado un tipo de rectángulo especializado llamado Cuadrado, que mantiene que el ancho debe ser igual a la altura en todo momento. Un cuadrado es un rectángulo, por lo que encaja con los principios de OO, ¿no?

Pero espera, ¿qué pasa si alguien escribe este método ahora?

public void Enlarge(Rectangle rect, double factor)
{
    rect.Width *= factor;
    rect.Height *= factor;
}

No está bien. Pero no hay razón para que el autor de este método haya sabido que podría haber un problema potencial.

Cada vez que derive una clase de otra, piense en la clase base y en lo que la gente podría asumir (como "tiene un ancho y una altura y ambas serían independientes"). Luego piense "¿esas suposiciones siguen siendo válidas en mi subclase?" Si no es así, reconsidere su diseño.

    
respondido por el pdr 17.10.2012 - 13:03