2009-03-19 19 views
19

Esiste una regola DesignForExtensionCheckstyle. Dice: se si dispone di un metodo pubblico/protetto che non è astratto né definitivo né vuoto, non è "progettato per l'estensione". Leggi lo description for this rule on the Checkstyle page per la logica.Come progettare per l'estensione

Immagina questo caso. Ho una classe astratta che definisce alcuni campi e un metodo validate per i campi:

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(); 
} 

Ho anche una sottoclasse di Plant:

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 
    } 
} 

Dopo la Checkstyle governare il Plant.validate (metodo) non è progettato per l'estensione. Ma come posso progettare l'estensione in questo caso?

+5

Non si dovrebbe gettare un IllegalArgumentException in un metodo che non accetta argomenti. .. – markt

+4

@markt Facciamo finta che sia IllegalStateException nell'interesse dell'argomento :) –

risposta

17

La regola si sta lamentando perché è possibile che una classe derivante (estensione) sostituisca completamente la funzionalità fornita senza informarla. È un'indicazione forte che non hai completamente considerato come il tipo potrebbe essere esteso. Che cosa vuole che tu faccia, invece, è qualcosa di simile:

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(); 
} 

Nota che oggi qualcuno può ancora fornire il proprio codice di validazione, ma non può sostituire il codice pre-scritto. A seconda di come intendevi usare il metodo validate, potresti anche renderlo pubblico finale.

+1

Vedo il punto, ma mi manca ancora un modo per implementare il metodo validate() "buono" e "pratico" allo stesso tempo. Se Tree implementa un validateEx finale() e chiama validate(), una classe che estende Tree (diciamo Forest) non sarà in grado di sovrascrivere validateEx(). Soluzione Implementare validateExEx()? –

+0

Usa un nome migliore, quindi. Forse ValidateTreeEx(). –

+2

Certo - non possono sostituire il tuo codice .. ma come può validare() essere mai chiamato? – markt

1

Sebbene la risposta di Joel Coehoorn spieghi come superare il problema concreto postato dall'OP, vorrei suggerire un approccio che offra una visione più ampia su "come progettare per l'estensione?" Come sottolinea l'OP in uno dei suoi commenti, la soluzione data non si adatta bene a una profondità di ereditarietà crescente (di classe). Inoltre, anticipando nella classe base la necessità di convalidare possibili classi figlio (validateTreeEx()) è problematico per ovvi motivi.

Proposta: verificare le proprietà delle piante al momento della costruzione e rimuovere validate() del tutto (insieme ai possibili setter, vedere anche http://www.javaworld.com/article/2073723/core-java/why-getter-and-setter-methods-are-evil.html). Il codice originale suggerisce che validate() è un invariante, che deve essere vero prima di ogni operazione grow(). Dubito che questo disegno sia intenzionale. Se non ci sono operazioni che possono "rompere" un impianto dopo la costruzione, non è necessario ricontrollare la validità più e più volte.

Andando ancora oltre, metterei in dubbio la solidità del progetto di ereditarietà iniziale. Senza operazioni aggiuntive (possibilmente polimorfiche), Tree riutilizza alcune proprietà di Plant. Sono fermamente convinto che l'ereditarietà delle classi non debba essere utilizzata per il riutilizzo del codice. Josh Bloch ha questo da dire (da Effective Java, 2nd Edition, capitolo 4):

Se si utilizza l'ereditarietà in cui la composizione è appropriata, si esporre inutilmente i dettagli di implementazione. L'API risultante ti lega a all'implementazione originale, limitando per sempre le prestazioni della classe . Più seriamente, esponendo gli interni si consente al client di accedervi direttamente.

controllare anche 'Articolo 17: Progettazione e documento per eredità o altro vietano esso' (anche il capitolo 4 per lo stesso libro)

+1

Grazie Horst, anche se la tua risposta discute il design del codice di esempio e non la domanda originale: come progettare per l'estensione. È davvero solo un codice di esempio, non perfetto probabilmente. –

Problemi correlati