wtorek, listopada 17, 2009

Giń konstruktorze! Giń! ;-)

Kartkuję sobie Effective Java i już pierwsza rada mi się spodobała :-) jest to rada "Rozważ użycie statycznej metody fabrykującej zamiast konstruktora".

To zdecydowanie ułatwia późniejszy refactoring, oraz poprawia naszą zdolność do samodzielnego zarządzania obiektami.

Jeżeli np. stworzenie obiektu jest kosztowne, a można go używać wielokrotnie to dzięki zastąpieniu konstruktora metodą fabrykującą będziemy w stanie utrzymywać np. pulę z obiektami i serwować je o wiele szybciej.
Także gdy nam obije i zapragniemy używać Singletona, albo np. jednej kopi obiektu na wątek [czasem się przydaje] to skorzystanie z metody fabrykującej zamiast konstruktora na pewno pomoże.

Innym aspektem jest łatwiejsza - czytaj wymagająca mniejszej ilości kodu - parametryzacja typu przy pomocy generyków [się rymło ;-)]

Np. teraz by stworzyć List<String> z ArrayList musimy zrobić to tak:
List<String> list = new ArrayList<String>();

Ale gdyby w klasie ArrayList zamiast konstruktora bezparametrowego była metoda:
 public static <E> List<E> createList() {
return new ArrayList<E>();
}

Wtedy moglibyśmy kod taki:

List<String> strings = new ArrayList<String>();
List<Integer> integers = new ArrayList<Integer>();

zastąpić takim:

List<String> strings = ArrayList.createList();
List<Integer> integers = ArrayList.createList();

A ten drugi jest zdecydowanie ładniejszy ;-)

Ogólnie takie podejście ułatwia późniejszą pracę z kodem, bo gdy zmienia nam się koncepcja to dzięki takiemu podejściu mamy większą kontrolę nad tym co się dzieje.
Możemy nawet zacząć serwować obiekty innego typu niż nasz jeśli akurat taki mamy kaprsy ;-) [byle ten nowo serwowany typ obiektów był zgodny z starym typem ;-)].

OK, są dwie wady takiego rozwiązania. Wada pierwsza to niemożność dziedziczenia po takiej klasie [da się to obejść, ale przy dzisiejszym przekonaniu, że dziedziczenie jest złe i kłopotliwe może i nie warto ;-)]
Druga wada jest bardziej subtelna i wynika z faktu, że nie ma sposobu na odróżnienie takiego metody fabrykującej od innych metod statycznych, ale to można załatwić nazwą.


Podobne postybeta
Konstruktory
Ile to jest 1+1 w Java'ie?
Trick w Java'ie ;-) czyli double brace initialization
Lenistwo w działaniu, "piklujemy" Androida ;-)
Niecne wykorzystanie refleksji... czyli jak poszukać tekstu w drzewie obiektów? ;-)

4 komentarze:

  1. 100% racji, widzę że warto sięgnąć po tę książkę. Chyba że chcesz ją tutaj streścić?:-)

    Jedyne z czym mogę się nie zgodzić, to twoje stwierdzenie, jakoby

    List<Integer> integers = ArrayList.createList();

    było 'zdecydowanie' ładniejsze od:

    List<Integer> integers = new ArrayList<Integer>();

    Czymś szczgólnym ta 'ładność' się wyraża (jest jakaś wartość dodana stosowania takiego zapisu), czy to tylko Twój 'dobry' gust?

    OdpowiedzUsuń
  2. Gust, a ładność oznacza tutaj tyle że nie trzeba parametryzować klasy w trakcie jej tworzenia. Java sama domyśli jak wywołać tą metodę na podstawie typu zmiennej do której jest przypisywany wynik.

    OdpowiedzUsuń
  3. Przemku, takie podejście ma więcej wad. Najpoważniejszą jest utrata kontroli nad procesem tworzenia obiektów. Mniejsza jeżeli to nasz obiekt, ale jeżeli korzystamy z biblioteki to możemy się bardzo boleśnie nadziać na jakieś niestandardowe rozwiązanie lub niespodziewanego i nigdzie nie udokumentowanego singletona. Względnie na jakiś hak z refleksją.
    Co do dziedziczenia to pamiętamy, że są 4 modyfikatory dostępu, a nie tylko dwa;)
    [code]
    class A {
    protected A(int i) {

    }

    static A a() {
    return new A(10);
    };
    }

    class B extends A {

    protected B(int i) {
    super(i);
    }

    static A a() {
    return new B(10);
    };
    }
    [/code]
    Nie wiem dlaczego wszyscy zapominają o protected.
    Co do odróżniania metod to zostaje tylko nieśmiertelna Konwencja nad Konfiguracją ;)

    Pozdrawiam
    Koziołek

    OdpowiedzUsuń
  4. Akurat ciężar decyzji jak powinny być tworzone obiekty powinien spoczywać na barkach autora obiektu, który wie co się dzieje w jego środku.
    Dodatkowo takie podejście pozwala na łatwy refaktoring i łatwą zmianę sposobu tworzenia obiektów. Przykład ;-) mamy kod, który "rozmawia" z bazą danych, to taka warstwa abstrakcji między aplikacją, a bazą, i niestety często jest tak, że ten sam rekord pobierany jest kilka razy w czasie jednego requesta. Gdyby zamiast tworzenia obiektów przy pomocy konstruktora można było używać w tym przypadku metod fabrykujących to łatbiej byłoby zaimplementować cache. A tak trzeba kombinować jak koń pod górę ;-)

    Co do modyfikatorów dostępu to są one świetne, ale mało kto ich używa, bo mało kto myśli o API dla pakietu czy w ogóle biblioteki, a zwykle myślimy na poziomie klasy.
    A na poziomie klasy wystarczą private i public, i w bardzo żadkich przypadkach protected. Ale już widoczność default ma sens dopiero gdy patrzymy na API z perspektywy pakietu.
    Widzisz to rozwiązanie, które pokazałeś ma tą wadę, że Twoje obiekty tworzone będą na 2 sposoby, musisz więc oprogramować obie strony [i jeszcze 3 czyli serializację].
    A co do refleksji, to one są jednak głównie do rzeczy niezwykłych. I co jest też doradzane w Effective Java, lepiej w przypadku refleksji robić tak, że jak już masz stworzony obiekt to lepiej go rzutować na znany interfejs, bo i wywoływanie będzie szybsze i bezpieczniejsze.

    OdpowiedzUsuń