2009-03-19 18 views
19

Hay una regla CheckstyleDesignForExtension. Dice: si tiene un método público/protegido que no es abstracto ni definitivo ni está vacío, no está "diseñado para la extensión". Lea el description for this rule on the Checkstyle page para la justificación.Cómo diseñar para la extensión

Imagine this case. Tengo una clase abstracta que define algunos campos y un método de validación para los campos:

public abstract class Plant { 
    private String roots; 
    private String trunk; 

    // setters go here 

    protected void validate() { 
     if (roots == null) throw new IllegalArgumentException("No roots!"); 
     if (trunk == null) throw new IllegalArgumentException("No trunk!"); 
    } 

    public abstract void grow(); 
} 

también tengo una subclase de la planta:

public class Tree extends Plant { 
    private List<String> leaves; 

    // setters go here 

    @Overrides 
    protected void validate() { 
     super.validate(); 
     if (leaves == null) throw new IllegalArgumentException("No leaves!"); 
    } 

    public void grow() { 
     validate(); 
     // grow process 
    } 
} 

Tras la Checkstyle gobernar el Plant.validate (método) no está diseñado para la extensión. ¿Pero cómo diseño la extensión en este caso?

+5

No debe tirar una IllegalArgumentException en un método que no tiene argumentos. .. – markt

+4

@markt Imaginemos que es IllegalStateException por el bien del "argumento" :) –

Respuesta

17

La regla se queja porque es posible que una clase derivada (extendida) reemplace completamente la funcionalidad que proporcionó sin informarle al respecto. Es una fuerte indicación de que no ha considerado completamente cómo podría extenderse el tipo. Lo que quiere que hagas en cambio, es algo como esto:

public abstract class Plant { 
    private String roots; 
    private String trunk; 

    // setters go here 

    private void validate() { 
     if (roots == null) throw new IllegalArgumentException("No roots!"); 
     if (trunk == null) throw new IllegalArgumentException("No trunk!"); 
     validateEx(); 
    } 

    protected void validateEx() { } 

    public abstract void grow(); 
} 

Tenga en cuenta que ahora alguien todavía puede proporcionar su propio código de validación, pero no puede reemplazar el código previamente escrito. Dependiendo de cómo haya querido usar el método validate, también podría hacerlo público final en su lugar.

+1

Veo el punto, pero aún me falta una forma de implementar el método validate() "bueno" y "práctico" al mismo tiempo. Si Tree implementa un final validateEx() y calls validate(), un árbol de extensión de clases (digamos Forest) no podrá anular validateEx(). Solution? Implement validateExEx()? –

+0

Use un nombre mejor, entonces. Tal vez ValidateTreeEx(). –

+2

Claro, no pueden reemplazar su código ... pero, ¿cómo se puede llamar a validate() alguna vez? – markt

1

Aunque la respuesta de Joel Coehoorn explica cómo superar el problema concreto publicado por el OP, me gustaría sugerir un enfoque que adopte una visión más amplia sobre '¿cómo diseñar para la extensión?' Como señala OP en uno de sus comentarios, la solución dada no escala bien con una profundidad de herencia creciente (de clase). Además, anticipando en la clase base, la necesidad de validar posibles clases secundarias (validateTreeEx()) es problemática por razones obvias.

Propuesta: Compruebe las propiedades de una planta en el momento de la construcción y elimine validate() en conjunto (junto con posibles incubadoras; ver también http://www.javaworld.com/article/2073723/core-java/why-getter-and-setter-methods-are-evil.html). El código original sugiere que validate() es un invariante, que tiene que ser verdadero antes de cada operación grow(). Dudo que este diseño sea intencional. Si no hay una operación que pueda "romper" una planta después de la construcción, no hay necesidad de volver a verificar la validez una y otra vez.

Yendo aún más lejos, me gustaría cuestionar la solidez del diseño de herencia inicial. Sin operaciones adicionales (posiblemente polimórficas), Tree solo reutiliza algunas propiedades de Plant. Tengo la firme opinión de que la herencia de clase no se debe usar para la reutilización de código. Josh Bloch tiene esto que decir (de Effective Java, versión 2, Capítulo 4):

Si utiliza la herencia, donde la composición es adecuada, que innecesariamente expone los detalles de implementación. La API resultante le ata a la implementación original, lo que siempre limita el rendimiento de su clase . Más en serio, al exponer las partes internas, permite que el cliente acceda directamente a ellas.

También puedes ver 'Tema 17: Diseño y documentos para la herencia o de lo contrario lo prohíben' (también el capítulo 4 para el mismo libro)

+1

Gracias Horst, aunque su respuesta discute el diseño del código de muestra y no la pregunta original: cómo diseñar para la extensión. Es solo un código de muestra, probablemente no perfecto. –

Cuestiones relacionadas