2009-03-19 5 views
19

Es gibt eine Checkstyle Regel DesignForExtension. Es heißt: Wenn Sie eine public/protected-Methode haben, die weder abstrakt noch final oder leer ist, ist sie nicht für die Erweiterung gedacht. Lesen Sie die description for this rule on the Checkstyle page für die Begründung.Howto Design für Erweiterung

Stellen Sie sich diesen Fall vor. Ich habe eine abstrakte Klasse, die für die Felder einige Felder und ein Validate-Methode definiert:

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

Ich habe auch eine Unterklasse von Pflanze:

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

die Check Nach der Plant.validate Regel() -Methode ist nicht für die Erweiterung gedacht. Aber wie entwerfe ich in diesem Fall die Erweiterung?

+5

Sie keine Illegal in einem Verfahren werfen sollte, die keine Argumente annimmt. .. – markt

+4

@markt Lassen Sie uns vortäuschen, es ist IllegalStateException für das "Argument" :) –

Antwort

17

Die Regel beschweren sich, weil es für eine ableitende (erweiternde) Klasse möglich ist, die von Ihnen bereitgestellte Funktionalität vollständig zu ersetzen, ohne Ihnen davon zu berichten. Es ist ein starkes Anzeichen dafür, dass Sie nicht vollständig darüber nachgedacht haben, wie der Typ erweitert werden könnte. Was sie will, dass du stattdessen zu tun ist so etwas wie dieses:

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

Beachten Sie, dass jetzt jemand noch ihre eigenen Validierungscode liefern können, aber sie können Ihre bereits geschriebenen Code nicht ersetzen. Je nachdem, wie Sie die Methode validate verwenden wollten, können Sie sie stattdessen auch als final veröffentlichen.

+1

Ich sehe den Punkt, aber mir fehlt noch eine Möglichkeit, die validate() -Methode "gut" und "praktisch" zur gleichen Zeit zu implementieren. Wenn Tree ein final validateEx() implementiert und validate() aufruft, kann eine Klasse, die Tree (also Forest) erweitert, nicht validateEx() überschreiben. Lösung? Implement validateExEx()? –

+0

Verwenden Sie einen besseren Namen dann. Vielleicht ValidateTreeEx(). –

+2

Sicher - sie können Ihren Code nicht ersetzen .. aber wie kann validate() jemals aufgerufen werden? – markt

1

Obwohl die Antwort von Joel Coehoorn erklärt, wie man das konkrete Problem des OP überwinden kann, möchte ich einen Ansatz vorschlagen, der einen breiteren Blick auf "wie für Erweiterung zu entwerfen?" Wie das OP darauf hinweist in einem seiner Kommentare skaliert die gegebene Lösung nicht gut mit einer wachsenden (Klassen-) Vererbungstiefe. Außerdem ist es aus offensichtlichen Gründen problematisch, in der Basisklasse zu erwarten, dass mögliche Kindklassen validiert werden müssen (validateTreeEx()).

Vorschlag: Überprüfen Sie die Anlageneigenschaften zum Zeitpunkt der Erstellung und entfernen Sie zusammen (zusammen mit möglichen Setter; siehe auch http://www.javaworld.com/article/2073723/core-java/why-getter-and-setter-methods-are-evil.html). Der ursprüngliche Code legt nahe, dass validate() eine Invariante ist, die vor jeder grow() Operation wahr sein muss. Ich bezweifle, dass dieses Design beabsichtigt ist. Wenn es keine Operation gibt, die eine Anlage nach der Konstruktion "brechen" kann, ist es nicht notwendig, die Gültigkeit immer wieder zu überprüfen.

Noch weiter gehen, würde ich die Solidität des anfänglichen Vererbungsentwurfs in Frage stellen. Ohne zusätzliche (möglicherweise polymorphe) Operationen verwendet Tree einfach einige Eigenschaften von Plant. Ich bin der festen Meinung, dass die Klassenvererbung nicht für die Wiederverwendung von Code verwendet werden sollte. Josh Bloch hat dies zu sagen (von Effective Java, 2. Auflage, Kapitel 4):

Wenn Sie Vererbung verwenden, wo Zusammensetzung geeignet ist, Sie unnötig Implementierungsdetails aus. Die resultierende API bindet Sie an die ursprüngliche Implementierung, für immer die Leistung von Ihre Klasse begrenzt. Ernsthafter, indem Sie die Interna aussetzen, lassen Sie den Client direkt auf sie zugreifen.

Überprüfen Sie auch ‚Punkt 17: Entwurf und Dokument für die Vererbung oder aber es verbieten‘ (siehe auch Kapitel 4 für das gleiche Buch)

+1

Danke Horst, obwohl deine Antwort das Design des Beispielcodes und nicht die ursprüngliche Frage diskutiert: wie für Erweiterung zu entwerfen. Es ist wirklich nur Beispielcode, wahrscheinlich nicht perfekt. –