2010-03-24 4 views
9

Es gibt verschiedene Möglichkeiten, wie ich komplexe Objekte initialisieren kann (mit eingefügten Abhängigkeiten und der erforderlichen Konfiguration von injizierten Membern), alle scheinen vernünftig zu sein, haben aber verschiedene Vor- und Nachteile. Ich werde ein konkretes Beispiel:Ist es eine gute oder schlechte Methode, Instanzmethoden von einem Java-Konstruktor aufzurufen?

final class MyClass { 
    private final Dependency dependency; 
    @Inject public MyClass(Dependency dependency) { 
    this.dependency = dependency; 
    dependency.addHandler(new Handler() { 
     @Override void handle(int foo) { MyClass.this.doSomething(foo); } 
    }); 
    doSomething(0); 
    } 
    private void doSomething(int foo) { dependency.doSomethingElse(foo+1); } 
} 

Wie Sie sehen können, der Konstruktor tut 3 Dinge, eine Instanz Methode einschließlich Aufruf. Mir wurde gesagt, dass das Aufrufen von Instanzmethoden aus einem Konstruktor unsicher ist, da es die Prüfungen des Compilers für nicht initialisierte Member umgeht. I.e. Ich hätte doSomething(0) vor der Einstellung this.dependency aufrufen können, die kompiliert hätte aber nicht funktioniert. Was ist der beste Weg, dies zu überdenken?

  1. Make doSomething statisch und übergeben Sie die Abhängigkeit explizit? In meinem tatsächlichen Fall habe ich drei Instanzmethoden und drei Mitgliedsfelder, die alle voneinander abhängig sind, so dass dies wie eine Menge zusätzlicher Vorkehrungen erscheint, um alle drei davon statisch zu machen.

  2. Verschieben Sie die addHandler und doSomething in eine @Inject public void init() Methode. Während die Verwendung mit Guice transparent sein wird, erfordert es eine manuelle Konstruktion, um sicher zu sein, init() zu rufen, sonst ist das Objekt nicht voll funktionsfähig, wenn jemand es vergisst. Dies macht auch mehr von der API frei, die beide wie schlechte Ideen erscheinen.

  3. Wrap eine verschachtelte Klasse, um die Abhängigkeit zu halten, dass es ohne auszusetzen zusätzliche API richtig zu machen verhält:

    class DependencyManager { 
        private final Dependency dependency; 
        public DependecyManager(Dependency dependency) { ... } 
        public doSomething(int foo) { ... } 
    } 
    @Inject public MyClass(Dependency dependency) { 
        DependencyManager manager = new DependencyManager(dependency); 
        manager.doSomething(0); 
    }
    Dies zieht aus allen Konstrukteuren Instanzmethoden, sondern eine zusätzliche Schicht von Klassen erzeugt, und wenn ich hatte schon inner und anonyme Klassen (zB der Handler) kann verwirrend werden - als ich das versuchte, wurde mir gesagt, ich solle die DependencyManager in eine separate Datei verschieben, was auch geschmacklos ist, weil es jetzt mehrere Dateien gibt, um eine einzige Sache zu machen.

Also, was ist der bevorzugte Weg, um mit dieser Art von Situation umzugehen?

+1

@Steve: Ich habe nur die ersten "Pre" -Tags entfernt, so dass der Code farbcodierte Syntax zeigt :) – SyntaxT3rr0r

+0

Cool, wusste nicht, dass es so funktioniert. – Steve

Antwort

8

Josh Bloch in Effektiv Java empfiehlt die Verwendung einer statischen Factory-Methode, obwohl ich kein Argument für solche Fälle finden kann. Es gibt jedoch einen ähnlichen Fall in Java Concurrency in Practice, der speziell dazu gedacht ist, zu verhindern, dass ein Verweis auf this aus dem Konstruktor herauskommt. In diesem Fall würde es so aussehen:

final class MyClass { 
    private final Dependency dependency; 

    private MyClass(Dependency dependency) { 
    this.dependency = dependency; 
    } 

    public static createInstance(Dependency dependency) { 
    MyClass instance = new MyClass(dependency); 
    dependency.addHandler(new Handler() { 
     @Override void handle(int foo) { instance.doSomething(foo); } 
    }); 
    instance.doSomething(0); 
    return instance; 
    } 
    ... 
} 

Dies funktioniert jedoch möglicherweise nicht gut mit der DI-Annotation, die Sie verwenden.

+0

Ich benutze Guice (naja, eigentlich Gin) - es unterstützt * nicht * statische Fabriken, aber es verhindert sie auch nicht - ich muss nur eine zusätzliche Kopie der Factory-Methode im 'GinModule' hinzufügen. Die andere Alternative besteht darin, eine "Provider " geschachtelte Klasse zu erstellen und "MyClass" als "@ProvidedBy (MyClass.MyProvider.class)" zu kommentieren. Ich hatte versucht, statische Fabrikmethoden zu vermeiden, da die Statik beim Testen viel Ärger verursachen kann, aber ich merkte, dass der Konstruktor tatsächlich das Gleiche ist. – Steve

0

Ja, es ist eigentlich illegal, es sollte wirklich nicht kompilieren auch (aber ich glaube, es tut)

die Erbauer Betrachten wir statt (und in Richtung unveränderlich anlehnen, die in Erbauer Begriffe bedeutet, dass Sie nicht anrufen jedes Setter zweimal und kann keinen Setter aufrufen, nachdem das Objekt "verwendet" wurde - das Aufrufen eines Setter an diesem Punkt sollte wahrscheinlich eine Laufzeitausnahme auslösen.

Sie können die Folien von Joshua Bloch auf dem (neu) Builder-Muster in einer Dia-Präsentation finden genannt "Effective Java Reloaded: Dieses Mal ist es für Real", zum Beispiel hier:

http://docs.huihoo.com/javaone/2007/java-se/

+1

Die Tatsache, dass es kompiliert, macht es nicht "illegal"; es gibt einen Unterschied zwischen schlechter Übung/"tue das nicht" und illegal –

+0

Du hast recht, ich habe wahrscheinlich das falsche Wort benutzt, aber jedes ordentliche Flusenprogramm würde dies als zumindest eine Warnung und möglicherweise einen Fehler anzeigen - ich Ich bin nicht sicher, warum sie es erlauben, aber dann bin ich mir immer noch nicht sicher, warum sie öffentlichen Mitgliedern in Java erlauben ... Es ist alles ein Mysterium. –

0

Sie können eine statische Methode verwenden, die die Abhängigkeit übernimmt, eine neue Instanz erstellt und zurückgibt und den Konstruktor Friend markiert. Ich bin mir nicht sicher, ob Friend in Java existiert (ist jedoch paketgeschützt). Dies ist jedoch möglicherweise nicht der beste Weg. Sie können auch eine andere Klasse verwenden, die eine Factory zum Erstellen von MyClass ist.

Edit: Wow, ein anderer Posted nur vorgeschlagen, genau diese gleiche Sache. Sieht so aus, als könnten Sie Konstruktoren in Java privat machen. Das können Sie in VB.NET nicht machen (nicht sicher über C#) ... sehr cool ...

9

Es vermasselt auch schlecht mit Vererbung. Wenn Ihr Konstruktor in der Kette aufgerufen wird, um eine Unterklasse Ihrer Klasse zu instanziieren, können Sie eine Methode aufrufen, die in der Unterklasse außer Kraft gesetzt wird und auf einer Invariante basiert, die erst nach dem Ausführen des Unterklassenkonstruktors erstellt wird.

+0

Guter Punkt - daran hatte ich nicht gedacht! In diesem Fall ist dies kein Problem, da 'MyClass'' final' ist, aber es ist etwas, das man im Kopf behalten sollte. – Steve

4

Sie sollten mit der Verwendung von Instanzmethoden innerhalb des Konstruktors vorsichtig sein, da die Klasse noch nicht vollständig erstellt wurde. Wenn eine aufgerufene Methode ein Element verwendet, das noch nicht initialisiert wurde, werden schlimme Dinge passieren.

Verwandte Themen