2010-03-24 10 views
9

Istnieje kilka różnych sposobów inicjowania złożonych obiektów (z wstrzykniętymi zależnościami i wymaganą konfiguracją wstrzykiwanych członków), wszystkie wydają się rozsądne, ale mają różne wady i zalety. Podam konkretny przykład:Czy dobrą lub złą praktyką jest wywoływanie metod instancji z konstruktora Java?

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

Jak widać, konstruktor robi 3 rzeczy, w tym wywołanie metody instancji. Powiedziano mi, że wywoływanie metod instancji z konstruktora jest niebezpieczne, ponieważ omija sprawdzenia kompilacji niezainicjowanych członków. To znaczy. Mogłem zadzwonić pod numer doSomething(0) przed ustawieniem this.dependency, który zostałby skompilowany, ale nie działał. Jaki jest najlepszy sposób, aby to zmienić?

  1. Ustawić doSomething statycznie i przekazać zależność jawnie? W moim rzeczywistym przypadku mam trzy metody instancji i trzy pola członkowskie, które wszystkie od siebie zależą, więc wydaje się, że jest wiele dodatkowych elementów, aby wszystkie te trzy statyczne.

  2. Przenieś addHandler i doSomething do metody @Inject public void init(). Podczas gdy użycie z Guice będzie przezroczyste, wymaga ręcznej konstrukcji, aby zadzwonić pod numer init(), inaczej obiekt nie będzie w pełni funkcjonalny, jeśli ktoś zapomni. Ponadto ujawnia to więcej API, z których oba wydają się złymi pomysłami.

  3. Wrap zagnieżdżona klasa utrzymać zależność aby upewnić się, że zachowuje się poprawnie, bez narażania dodatkowe API:

    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); 
    }
    To ciągnie instancji metod spośród wszystkich konstruktorów, ale generuje dodatkową warstwę klas, a kiedy już wewnętrzne i anonimowe klasy (np. ten handler) może to być mylące - kiedy próbowałem, powiedziano mi, aby przenieść DependencyManager do osobnego pliku, co jest również niesmaczne, ponieważ jest teraz wiele plików, aby zrobić jedną rzecz.

Jaki jest preferowany sposób radzenia sobie z tego rodzaju sytuacją?

+1

@Steve: Właśnie usunąłem pierwsze znaczniki "pre", aby kod wyświetlał się za pomocą kolorowej składni :) – SyntaxT3rr0r

+0

Fajnie, nie wiedziałem, że zadziałało w ten sposób. – Steve

Odpowiedz

8

Josh Bloch w Effective Java zaleca używanie statycznej metody fabrycznej, chociaż nie mogę znaleźć żadnego argumentu dla takich przypadków. Istnieje jednak podobny przypadek pod numerem Java Concurrency in Practice, który ma na celu uniknięcie odniesienia do obiektu this z konstruktora. Zastosowane w niniejszej sprawie, to będzie wyglądać:

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

to jednak może nie działać dobrze z dopiskiem DI używasz.

+0

Używam Guice (właściwie Gin) - to nie * bezpośrednio * obsługuje statyczne fabryki, ale to też im nie przeszkadza - po prostu potrzebuję dodać dodatkową kopię metody fabrycznej w 'GinModule'. Inną alternatywą jest utworzenie klasy '' Provider 'i przypisanie' MyClass' jako '@ProvidedBy (MyClass.MyProvider.class)'. Próbowałem unikać statycznych metod fabrycznych, ponieważ statyka może powodować wiele problemów z testowaniem, ale zdałem sobie sprawę, że konstruktor jest faktycznie tym samym. – Steve

0

Tak, to faktycznie nielegalne, to naprawdę nie powinien nawet skompilować (ale wierzę, że robi)

Rozważmy budowniczy zamiast wzoru (i skłaniają się ku niezmienne który w budowniczy względem wzoru oznacza, że ​​nie można nazwać dowolny setter dwa razy i nie może wywołać żadnego settera po tym, jak obiekt został "użyty" - wywołanie setera w tym momencie powinno prawdopodobnie spowodować wyjątek środowiska wykonawczego).

można znaleźć slajdy przez Joshua Bloch o (nowy) wzór Builder w formie pokazu slajdów o nazwie „Efektywne Java Reloaded: Tym razem jest to naprawdę”, na przykład tutaj:

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

+1

Fakt, że kompiluje, sprawia, że ​​nie jest "nielegalny"; jest jakaś różnica między złą praktyką/"nie rób tego" a nielegalnym –

+0

Masz rację, prawdopodobnie użyłem niewłaściwego słowa, ale jakikolwiek przyzwoity program lint pokazałby to jako przynajmniej ostrzeżenie i prawdopodobnie błąd - I nie jestem pewien, dlaczego na to pozwalają, ale nadal nie jestem pewien, dlaczego dopuszczają członków publicznych w Java ... To wszystko jest tajemnicą. –

0

Można użyć statycznej metody, która pobiera zależność i konstruuje i zwraca nową instancję, a następnie zaznacz konstruktora Friend. Nie jestem pewien, czy Friend istnieje w Javie (czy jest to pakiet chroniony). Może nie jest to jednak najlepsza metoda. Możesz także użyć innej klasy, która jest Factory do tworzenia MyClass.

Edytuj: Wow inny wysłany właśnie zasugerował dokładnie to samo. Wygląda na to, że można konstruować konstruktory jako prywatne w Javie. Nie możesz tego zrobić w VB.NET (nie jestem pewien co do C#) ... bardzo fajnie ...

9

To również bardzo źle wpływa na dziedziczenie. Jeśli twój konstruktor jest wywoływany w łańcuchu, aby utworzyć instancję klasy, możesz wywołać metodę, która jest nadpisywana w podklasie i opiera się na niezmienniku, który nie został ustanowiony, dopóki konstruktor podklasy nie zostanie uruchomiony.

+0

Dobra uwaga - nie myślałem o tym! W tym przypadku nie stanowi to problemu, ponieważ 'MyClass' jest' final', ale należy o tym pamiętać. – Steve

4

Należy zachować ostrożność w używaniu metod instancji z poziomu konstruktora, ponieważ klasa ta nie została jeszcze w pełni skonstruowana. Jeśli wywoływana metoda używa członka, który nie został jeszcze zainicjalizowany, cóż, złe rzeczy się wydarzą.