2012-05-28 8 views
13

Estoy en una situación muy similar a la que ha mencionado Steve McConnell's en Code Complete. Solo que mi problema está basado en Vehículos y Trike pasa a estar en eso por ley cae en la categoría de Autos. Los autos tenían cuatro ruedas hasta ahora. De todos modos, mi dominio es innecesariamente complejo, por lo que es fácil seguir el siguiente ejemplo de gatos.¿Cómo puedo evitar romper el Principio de sustitución de Liskov (LSP)?

sospechar de clases que anulan una rutina y no hacen nada dentro de la rutina derivada Normalmente, esto indica un error en el diseño de la clase base. Por ejemplo, supongamos que tiene una clase Cat y una rutina Scratch() y suponga que finalmente descubre que algunos gatos están destripados y no pueden rayar. Es posible que tenga la tentación de crear una clase derivada de Cat llamada ScratchlessCat y anular la rutina Scratch() para no hacer nada. Este enfoque presenta varios problemas:

Viola la abstracción (contrato de interfaz) presentada en la clase Cat al cambiar la semántica de su interfaz.

Este enfoque se sale de control rápidamente cuando lo extiende a otras clases derivadas . ¿Qué pasa cuando encuentras un gato sin cola? O un gato que no atrapa ratones? ¿O un gato que no bebe leche? Eventualmente terminarás con clases derivadas como ScratchlessTaillessMicelessMilklessCat.

Con el tiempo, este enfoque da lugar a código que confunde a mantener porque las interfaces y el comportamiento de las clases antecesoras implican poco o nada sobre el comportamiento de sus descendientes.

El lugar para solucionar este problema no está en la clase base, sino en la clase Cat original . Cree una clase Claws y contenga eso dentro de la clase Cats . El problema de raíz fue la suposición de que todos los gatos se rascan, , por lo tanto, solucione ese problema en la fuente, en lugar de simplemente vendarlo en el destino .

De acuerdo con el texto de su gran libro anterior. Lo que sigue es mala

clase principal no tiene que ser abstracta

public abstract class Cat { 
    public void scratch() { 
     System.out.println("I can scratch"); 
    } 
} 

clase derivada

public class ScratchlessCat extends Cat { 
    @Override 
    public void scratch() { 
     // do nothing 
    } 
} 

Ahora se sugiere la creación de otra clase Claws, pero yo no entiendo cómo puede Uso esta clase para evitar la necesidad de ScratchlessCat#Scratch.

+2

El ejemplo del gato es una buena ilustración de la LSP. Al conocer LSP, me sorprendió ver cuántos ejemplos pobres y cuántos malentendidos hay en SO y otros sitios. – SteveT

Respuesta

8

todavía tendría un método scratch(), pero no va a ser anulado por las clases derivadas:

public class Cat { 
    Claw claw_; 
    public Cat(Claw claw) {claw = claw_;} 
    public final void scratch() { 
    if (claw_ != null) { 
     claw_.scratch(this); 
    } 
    } 
} 

Esto le permite delegar la lógica arañar a la Claw objeto contenido, si está presente (y no rasca y gana si no hay garras). Las clases derivadas del gato no tienen voz en el asunto sobre cómo rayar, por lo que no es necesario crear jerarquías ocultas basadas en habilidades.

Además, como las clases derivadas no pueden cambiar la implementación del método, no hay problema de que rompan la semántica prevista del método scratch() en la interfaz de la clase base.

Si lleva esto a los extremos, puede encontrar que tiene muchas clases y no muchas derivaciones: la mayor parte de la lógica se delega a los objetos de composición, no a las clases derivadas.

11

Que no todos los gatos tienen garras y son capaces de rascarse es una gran pista de que Cat no debe definir un método público scratch en su API. El primer paso es considerar por qué definió scratch en primer lugar. Tal vez los gatos se rasquen si pueden cuando son atacados; si no silban o escapan.

public class Cat extends Animal { 
    private Claws claws; 

    public void onAttacked(Animal attacker) { 
     if (claws != null) { 
      claws.scratch(attacker); 
     } 
     else { 
      // Assumes all cats can hiss. 
      // This may also be untrue and you need Voicebox. 
      // Rinse and repeat. 
      hiss(); 
     } 
    } 
} 

Ahora puede sustituir cualquier Cat subclase de otra y se comportará correctamente en función de si tiene o no tiene garras. Se podría definir una clase DefenseMechanism para organizar las diversas defensas tales como Scratch, Hiss, Bite, etc.

Cuestiones relacionadas