Czy nadużywamy metod statycznych?


13

Kilka miesięcy temu zacząłem pracować nad nowym projektem, a kiedy przejrzałem kod, uderzyło mnie ilość użytych metod statycznych. collectionToCsvString(Collection<E> elements)Przechowuje się w nich nie tylko metody użytkowe , ale także mnóstwo logiki biznesowej.

Kiedy zapytałem faceta odpowiedzialnego za uzasadnienie tego, powiedział, że to sposób na ucieczkę od tyranii Springa . Obejmuje to coś w tym procesie myślenia: w celu wdrożenia metody tworzenia paragonu klienta moglibyśmy mieć usługę

@Service
public class CustomerReceiptCreationService {

    public CustomerReceipt createReceipt(Object... args) {
        CustomerReceipt receipt = new CustomerReceipt();
        // creation logic
        return receipt;
    }
}

Facet powiedział, że nie lubi niepotrzebnie zarządzać klasami przez Spring, głównie dlatego, że nakłada to ograniczenie, że klasy klientów muszą być same ziarenkami Spring. W końcu wszystko zarządza Spring, co zmusza nas do pracy z obiektami bezpaństwowymi w sposób proceduralny. Mniej więcej to, co podano tutaj https://www.javacodegeeks.com/2011/02/domain-driven-design-spring-aspectj.html

Więc zamiast powyższego kodu ma

public class CustomerReceiptCreator {

    public static CustomerReceipt createReceipt(Object... args) {
        CustomerReceipt receipt = new CustomerReceipt();
        // creation logic
        return receipt;
    }
}

Mógłbym kłócić się do tego stopnia, że ​​w miarę możliwości unikam Springowego zarządzania naszymi klasami, ale nie widzę korzyści z posiadania wszystkiego statycznego. Te metody statyczne są również bezpaństwowe, więc też nie bardzo OO. Czułbym się bardziej komfortowo z czymś takim

new CustomerReceiptCreator().createReceipt()

Twierdzi, że metody statyczne mają dodatkowe zalety. Mianowicie:

  • Łatwiejszy do odczytania. Zaimportuj metodę statyczną i musimy tylko dbać o akcję, bez względu na to, która klasa to robi.
  • Jest oczywiście metodą wolną od wywołań DB, więc pod względem wydajności jest tania; dobrze jest to wyjaśnić, aby potencjalny klient musiał wejść do kodu i to sprawdzić.
  • Łatwiejsze pisanie testów.

Ale po prostu czuję, że jest w tym coś nie tak, więc chciałbym usłyszeć o tym bardziej doświadczone opinie programistów.

Moje pytanie brzmi: jakie są potencjalne pułapki tego sposobu programowania?




4
Powyższa staticmetoda jest zwykłą metodą fabryczną. Uczynienie metod fabrycznych statycznymi jest ogólnie przyjętą konwencją z wielu istotnych powodów. To, czy metoda fabryczna jest tutaj właściwa, to inna sprawa.
Robert Harvey,

Odpowiedzi:


23

Jaka jest różnica między new CustomerReceiptCreator().createReceipt()i CustomerReceiptCreator.createReceipt()? Prawie żaden. Jedyną znaczącą różnicą jest to, że pierwszy przypadek ma znacznie bardziej niezręczną składnię. Jeśli zastosujesz się do pierwszego w przekonaniu, że unikanie metod statycznych sprawia, że ​​Twój kod jest lepszy OO, poważnie się mylisz. Tworzenie obiektu tylko w celu wywołania na nim pojedynczej metody jest metodą statyczną według składni rozwartej.

Sprawy wyglądają inaczej, gdy wstrzykujesz je CustomerReceiptCreatorzamiast newje. Rozważmy przykład:

class OrderProcessor {
    @Inject CustomerReceiptCreator customerReceiptCreator;

    void processOrder(Order order) {
        ...
        CustomerReceipt receipt = customerReceiptCreator.createReceipt(order);
        ...
    }
}

Porównajmy to statyczna wersja metody:

void processOrder(Order order) {
    ...
    CustomerReceipt receipt = CustomerReceiptCreator.createReceipt(order);
    ...
}

Zaletą wersji statycznej jest to, że mogę łatwo powiedzieć, w jaki sposób współdziała ona z resztą systemu. Tak nie jest. Jeśli nie użyłem zmiennych globalnych, wiem, że reszta systemu nie została jakoś zmieniona. Wiem, że żadna inna część systemu nie może mieć wpływu na paragon. Jeśli użyłem niezmiennych obiektów, wiem, że kolejność się nie zmieniła, a createReceipt jest czystą funkcją. W takim przypadku mogę swobodnie przenosić / usuwać / etc to połączenie bez obawy o przypadkowe nieprzewidywalne efekty w innym miejscu.

Nie mogę dać takich samych gwarancji, jeśli wstrzyknąłem CustomerReceiptCreator. Może mieć stan wewnętrzny, który jest zmieniany przez połączenie, może mieć na mnie wpływ inny stan lub go zmienić. W mojej funkcji mogą występować nieprzewidywalne relacje między instrukcjami, tak że zmiana kolejności spowoduje zaskakujące błędy.

Z drugiej strony, co się stanie, jeśli CustomerReceiptCreatornagle będzie potrzebować nowej zależności? Powiedzmy, że musi sprawdzić flagę funkcji. Gdybyśmy wstrzykiwali, moglibyśmy zrobić coś takiego:

public class CustomerReceiptCreator {
    @Injected FeatureFlags featureFlags;

    public CustomerReceipt createReceipt(Order order) {
        CustomerReceipt receipt = new CustomerReceipt();
        // creation logic
        if (featureFlags.isFlagSet(Flags::FOOBAR)) {
           ...
        }
        return receipt;
    }
}

Następnie jesteśmy skończeni, ponieważ kod wywołujący zostanie wstrzyknięty za pomocą, CustomerReceiptCreatorktóry zostanie automatycznie wstrzyknięty FeatureFlags.

Co jeśli użyjemy metody statycznej?

public class CustomerReceiptCreator {
    public static CustomerReceipt createReceipt(Order order, FeatureFlags featureFlags) {
        CustomerReceipt receipt = new CustomerReceipt();
        // creation logic
        if (featureFlags.isFlagSet(Flags::FOOBAR)) {
           ...
        }
        return receipt;
    }
}

Ale poczekaj! kod telefoniczny również wymaga aktualizacji:

void processOrder(Order order) {
    ...
    CustomerReceipt receipt = CustomerReceiptCreator.createReceipt(order, featureFlags);
    ...
}

Oczywiście wciąż pozostaje pytanie, skąd się processOrderbierze FeatureFlags. Jeśli mamy szczęście, szlak kończy się tutaj, jeśli nie, konieczność przejścia przez FeatureFlags zostaje przesunięta w górę stosu.

Tu jest kompromis. Metody statyczne wymagające jawnego przekazywania zależności, co powoduje więcej pracy. Metoda wstrzykiwana zmniejsza pracę, ale czyni domniemanie zależności, a tym samym ukrywa, czyniąc kod trudniejszym do uzasadnienia.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.