Wstrzykiwanie serwisu czy statyczne metody

0

Ostatnio pisałem sobie program w GUI i wyświetlanie alertów wyodrębniłem do osobnej klasy. Były tam metody, które wyświetlały różne okienka powiadomień. Było też kilka innych tak jakby klas-serwisów. I w sumie wszystko zacząłem wstrzykiwać do klas, w których potrzebowałem danego serwisu. Zastanawiam się jednak czy nie wygodniej byłoby jakby klasa AlertsService (czy inne, które nie trzymają żadnego stanu) udostępniała statyczne metody do wyświetlania okienek? Skąd wiedzieć kiedy lepiej wstrzyknąć serwis, a kiedy stworzyć statyczną metodę? Są jakieś zasady / dobre praktyki co kiedy?

2

Pomyśl (albo poczytaj w necie, jeśli nic ci nie przychodzi do głowy) jakie zalety i wady ma wstrzykiwanie serwisów /*/ , a jakie zalety i wady ma zrobienie singletona/zmiennej globalnej ze statycznymi metodami. I pomyśl jakie inne rozwiązania widzisz.

A następnie spróbuj pomyśleć jakie mogą być konsekwencje. Co się stanie jak wybierzesz tę metodę? Spróbuj wyobrazić sobie w jaki sposób będziesz chciał rozwijać swój program za tydzień czy za miesiąc. Która metoda będzie bardziej odpowiednia? (jeśli nie wiesz, to możesz zrobić eksperyment i wybrać którąkolwiek metodę i potem już będziesz bardziej doświadczony, nawet jeśli wybierzesz złą, albo szczególnie jeśli wybierzesz złą - człowiek uczy się na błędach).`

Skąd wiedzieć kiedy lepiej wstrzyknąć serwis, a kiedy stworzyć statyczną metodę?

Za pomocą wyobraźni. Spróbuj sobie wyobrazić konsekwencje swoich wyborów.

Są jakieś zasady / dobre praktyki co kiedy?

Zasady i dobre praktyki są wymyślane przez ludzi, a nie są niczym inherentnym. Ważniejsze są konsekwencje danych wzorców, ich korzyści i ograniczenia (a to można łatwo w necie znaleźć, ponieważ popularne wzorce są dość dobrze opisane).

/*/ np. wpisujesz w google: advantages disadvantages dependency injection i czytasz. Oczywiście nie znaczy to, żeby zawsze wierzyć bezgranicznie to, co się wyczyta. Najlepiej samemu sprawdzić w praktyce.

3

Statyczna metoda ma ten minus że nie może być polimorficzna i generalnie jeśli sam pobierasz zależności to trudno je potem "podmienić" czy to inne implementacje czy to na jakieś stuby w trakcie testów.

3

Jest inna droga - wrzucanie zależności przez konstruktor - czyli dependency injection. Nie potrzebujesz kontenera (Spring) i prosto możesz wszystko testować w Javie.
Jest niewiele przypadków gdzie metody statyczne się sprawdzają - jakieś konwertery, proste obliczenia itp. (gdzie wiadomo, że nawet na potrzeby testów nie ma sensu metody inaczej implementować).

3
Naitoreivun napisał(a):

Są jakieś zasady / dobre praktyki co kiedy?

  1. jeśli implementujesz jakieś uniwersalne funkcje w stylu sinus, cosinus - to jest duża szansa że wystarczy Ci jedna wersja w aplikacji (w ogóle w bazie kodu) - wtedy możesz użyć utility class
  2. jeśli zależność wstrzykujesz 2-5 razy w całej aplikacji to wystarczy "ręczne" wstrzyknięcie przez konstruktor
  3. jeśli aplikacja jest mała ale liczba wstrzykiwań idzie w setki (np. połączenie z bazą) to lepiej zastosować poniższe:
    3.1) jeśli implementujesz serwis który może być tylko jeden w aplikacji - singleton
    3.2) jeśli wstrzykiwany obiekt może mieć warianty, lepiej zastosować registry bo możesz wykorzystać kilka różnych wersji singletona
  4. gdy aplikacja jest już spora, to lepiej to jakoś ogarnąć odgórnie (np. przez kontener IoC, ustandaryzowany XML lub cokolwiek co ma jakąś architekturę, którą można zmieniać przekrojowo w całej aplikacji - np. dla wszystkich klas implementujących interfejs X robić krok Y przy tworzeniu obiektu)
0

Bardzo dziękuję za wszystkie rady :)

Jeszcze żeby rozwiać ewentualne wątpliwości: mówiąc o ręcznym wstrzykiwaniu macie na myśli żeby w mainie potworzyć odpowiednie instancje klas i ładnie je sobie przekazywać do konstruktorów innych klas, a dopiero potem ruszyć całą aplikację?


Jeśli chodzi o Springa to bardzo polubiłem różne jego moduły, szczególnie Spring Data, więc nawet jeśli nie robię aplikacji webowej, to dla uproszczenia łączenia się z bazą, robienia zapytań, konfiguracji wszystkiego (dziękuję Pan Spring Boot) to lubię go dodać do aplikacji, więc skoro DI jest w zestawie, to też go chętnie użyję.

0

Ale jak już DI Springa to posłuchaj chociaż jednego z twórców i rób to przez konstruktor.
http://olivergierke.de/2013/11/why-field-injection-is-evil/

0

To jeszcze ja się podepnę pod temat z kilkoma pytaniami. Android Studio / Java.

1. Ściągnąłem sobie ostatnio przykład jakiejś aplikacji z oficjalnego gita GoogleSamples (nie pamiętam dokładnie czy to był ten, ale jeden z takich, który osoba bez doświadczenia wzięła by za wzór no bo od google itp.), i nagminnie używają tam konstrukcji:

(przykładowa nazwa)
public class NetworkManager {
    private static NetworkManager mNetworkManager = null;

<EDIT -> zapomniałbym, konstruktor domyślny prywatny>

    public static NetworkManager getInstance() {
        if (mNetworkManager == null) {
            mNetworkManager = new NetworkManager();
        }
        return mNetworkManager;
    }
...reszta metod niestatyczna
}

Jak to ma się w stosunku do pól i metod statycznych? Czy taka klasa jest singletonem?
Pytam, bo wrzucano to było prawie wszędzie, do klas rodzaju model/manager, a ze względu, że jest to static, inicjowane będzie tylko raz.
W ogóle dobra/zła praktyka?

2. Odnośnie metod statycznych i klas utility.
Jakie powinno być podejście do klas posiadających tylko metody statyczne, nie przechowujących i zapisujących żadnych zmiennych jako swoje pola, działających na zasadzie: podanie parametrów wejściowych -> klasa wykonuje operacje/ konwersje/manipulacje itp -> zwrócenie nowej poprawnej wartości.
Święcona woda i egzorcyzmy, czy ma to może zastosowanie?
Pytam w kontekście tego, że mogą być przeprowadzone jakieś bardziej zaawansowane operacje, ale z trzymaniem zasady parametr wejściowy -> operacje -> zwrotny parametr wyjściowy, oraz klasa sama w sobie nie ma żadnych zmiennych.
Przykładowo, konwersja współrzędnej geograficznej z double to string ze zmianą z postaci numerycznej na czasową (czy jak tam zwał ten format):

class JakaśKlasa {

double latitude = 51.321321231321321;
String str = FormatAndValueConventer.setGeoCoordinateStr(latitude, true);

System.out.println(str);
<wyświetla wartość np. 51°23'13''N*>
}


class FormatAndValueConventer {
    /** Set geographical coordinate string
     * @param coordinate geographic coordinate to forge
     * @param isLatitude define if given coordinate is latitude or longitude
     * @return formatted geographical coordinate string
     * example: 51°23'13''N*
     */
    public static String setGeoCoordinateStr(Double coordinate, boolean isLatitude) {
        String str;
        str = convertCoordinateToStr(coordinate);
        str = replaceSpecialSigns(str);

        int indexOfDoubleApostropheInStr;
        indexOfDoubleApostropheInStr = getIndexOfDoubleApostropheInStr(str);
        str = subtractStr(str, indexOfDoubleApostropheInStr);
        str = setGeoDirectionSymbol(str, coordinate, isLatitude);

        return str;
    }

    private static String convertCoordinateToStr(Double coordinate) {
        return Location.convert(coordinate, Location.FORMAT_SECONDS);
    }

    private static String replaceSpecialSigns(String str) {
        if (str.contains(":")) {
            str = str.replaceFirst(":", "°");
        }
        if (str.contains(":")) {
            str = str.replaceFirst(":", "'");
        }
        if (str.contains(",")) {
            str = str.replaceFirst(",", "''");
        }
        return str;
    }

    private static int getIndexOfDoubleApostropheInStr(String str) {
        if (str.contains("''")) {
            return str.indexOf("''");
        } else {
            return str.length() + 1;
        }
    }

    private static String subtractStr(String str, int indexOfSignInStr) {
        if (indexOfSignInStr < str.length() && str.contains("''")) {
            return str.substring(0, indexOfSignInStr+2);
        } else {
            return str;
        }
    }

    private static String setGeoDirectionSymbol(String str, Double coordinate, boolean isLatitude) {
        if (isLatitude) {
            return appendSymbolForLatitude(str, coordinate);
        } else {
            return appendSymbolForLongitude(str, coordinate);
        }
    }

    private static String appendSymbolForLatitude(String str, Double coordinate) {
        if (coordinate < 0) {
            return str + "S";
        } else {
            return str + "N";
        }
    }

    private static String appendSymbolForLongitude(String str, Double coordinate) {
        if (coordinate < 0) {
            return str + "W";
        }
        else {
            return str + "E";
        }
    }
}
0

Ogólnie to statiki nie są takie złe jak je malują jeśli nie manipulują stanem i nie wprowadzają efektów ubocznych. Twój przykład byłby prawie dobry... ale jest tu inny antypattern.
Jeśli masz jakieś Geokordynaty to opakuj te Double czy Stringi w klasę Geocoord i tego używaj. Inaczej pałętasz się przez cały system z jakimiś Doublami i Stringami i nikt nie wie o co chodzi. W Kotlinie albo Scali jest nawet Type alias. Czyli możemy sobie powiedzieć, że Double to Geocoord i tego używać (mimo, że pod spodem jest np. całkiem zwykły Double).

0

@jarekr000000 Dzięki za szybką odpowiedź.

A jak z pierwszym pytaniem? Tego rodzaju klasa to singleton, czy może nie?

*A, i jak coś opakowane już jest w klasę Location z biblioteki androida, tylko tutaj wpisałem double. :)

1

Tak - to paskudny singleton. Niestety raczej w takim przypadku antypattern - utrudnia testowanie (no bo jeśli np. na potrzeby testów chcemy mieć NetworkManagera, który bez Networka działa... ) i ogólnie potrafi rozwalić design (bo wszędzie można wrzucić wywołanie). To jest nawet dwa poziomy niebezpieczniejsze niż Beany / Singletony Springowe.
Czasami Singleton jest OK. Ale to raczej bardzo, bardzo rzadko i w dość technicznych przypadkach np. jakiś LoggerFactory, SysConfig (tu już bywa, że słabo) czy tym podobne.

0

@jarekr000000: to jak masz globalnego configa apki jakbyś bez singletonów z tym handlował?

0

Mario Fusco mówi Reader Monad - ja się jakoś bez niczego specjalnego obywam. (Raczej widzę, że za często Clock wstrzykuję -> i tu chyba coś wykombinuje).

Generalnie jak coś zaczyna być wstrzykiwane wszędzie (albo w wiele miejsc) to znak, że coś poszło nie tak. Z Configiem sobie radzę, żeby takiej miazgi nie mieć.
(No ale z Clockiem nie - rozłazi mi się).

1 użytkowników online, w tym zalogowanych: 0, gości: 1