Oswajanie klas „funkcji narzędziowych”

15

W naszej bazie kodu Java wciąż widzę następujący wzorzec:

/**
 This is a stateless utility class
 that groups useful foo-related operations, often with side effects.
*/
public class FooUtil {
  public int foo(...) {...}
  public void bar(...) {...}
}

/**
 This class does applied foo-related things.
*/
class FooSomething {
  int DoBusinessWithFoo(FooUtil fooUtil, ...) {
    if (fooUtil.foo(...)) fooUtil.bar(...);
  }
}

Niepokoi mnie to, że muszę zdać instancję FooUtilwszędzie, ponieważ testowanie.

  • Nie mogę uczynić FooUtilmetod statycznymi, ponieważ nie będę w stanie wyśmiewać ich do testowania obu FooUtilklas klienta.
  • Nie mogę utworzyć instancji FooUtilw miejscu konsumpcji za pomocą new, ponownie, ponieważ nie będę w stanie wyśmiewać jej do testów.

Przypuszczam, że moim najlepszym rozwiązaniem jest zastrzyk (i robię to), ale dodaje on własny zestaw problemów. Ponadto przekazanie kilku instancji użytkownika powoduje wzrost listy list parametrów metod.

Czy istnieje sposób, aby lepiej sobie z tym poradzić, czego nie widzę?

Aktualizacja: ponieważ klasy narzędzi są bezstanowe, prawdopodobnie mógłbym dodać statyczny INSTANCEelement singletonu lub getInstance()metodę statyczną , zachowując możliwość aktualizacji podstawowego pola statycznego klasy do testowania. Nie wydaje się też super-czysty.

9000
źródło
4
Twoje założenie, że wszystkie zależności muszą być wyśmiewane, aby skutecznie przeprowadzać testy jednostkowe, nie jest poprawne (chociaż wciąż szukam pytania, które to omawia).
Telastyn
4
Dlaczego te metody nie są członkami klasy, których danymi manipulują?
Jules
3
Mają osobny zestaw Junit, który wywołuje statyczny fns, jeśli FooUtility. Następnie możesz go używać bez kpin. Jeśli jest coś, co musisz wyśmiewać, podejrzewam, że jest to nie tylko narzędzie, ale także logika biznesowa lub logiczna i faktycznie powinno być referencją członka klasy i być inicjowane za pomocą konstruktora, metody init lub metody ustawiającej.
tgkprog,
2
+1 za komentarz Julesa. Klasy Util to zapach kodu. W zależności od semantyki powinno być lepsze rozwiązanie. Może FooUtilmetody powinny zostać wprowadzone FooSomething, może istnieją właściwości, FooSomethingktóre należy zmienić / rozszerzyć, aby FooUtilmetody nie były już potrzebne. Podaj nam bardziej konkretny przykład tego, co FooSomethingpomoże nam udzielić dobrej odpowiedzi na te pytania.
valenterry
13
@valenterry: Jedynym powodem, dla którego klasy util mają zapach kodu, jest to, że Java nie zapewnia dobrego sposobu na samodzielne funkcje. Istnieją bardzo dobre powody, aby mieć funkcje, które nie są specyficzne dla klas.
Robert Harvey

Odpowiedzi:

16

Przede wszystkim powiem, że istnieją różne podejścia programistyczne dla różnych problemów. W mojej pracy wolę mocny projekt OO do organizacji i konserwacji, a moja odpowiedź to odzwierciedla. Podejście funkcjonalne miałoby zupełnie inną odpowiedź.

Zwykle stwierdzam, że większość klas użyteczności jest tworzona, ponieważ obiekt, na którym powinny się znajdować metody, nie istnieje. To prawie zawsze pochodzi z jednego z dwóch przypadków, albo ktoś przekazuje kolekcje lub ktoś przekazuje fasolę (Są to często nazywane POJO, ale uważam, że grupę seterów i pobierających najlepiej opisać jako fasolę).

Kiedy masz kolekcję, którą przekazujesz, musi istnieć kod, który chce być częścią tej kolekcji, co kończy się rozsypywaniem w całym systemie i ostatecznie gromadzeniem w klasy użytkowe.

Moją sugestią jest zawinięcie kolekcji (lub fasoli) innymi ściśle powiązanymi danymi w nowe klasy i umieszczenie kodu „narzędziowego” w rzeczywistych obiektach.

Możesz rozszerzyć kolekcję i dodać tam metody, ale w ten sposób tracisz kontrolę nad stanem obiektu - sugeruję, aby całkowicie ją zamknąć i ujawnić tylko niezbędne metody logiki biznesowej (Pomyśl „Nie pytaj obiektów o ich dane, wydawaj swoim obiektom polecenia i pozwól im działać na ich prywatnych danych ”, ważnym najemcy OO i takim, który, gdy się nad tym zastanowić, wymaga podejścia, które sugeruję tutaj).

To „zawijanie” jest przydatne dla dowolnych obiektów biblioteki ogólnego przeznaczenia, które przechowują dane, ale nie można dodawać kodu - Umieszczanie kodu z danymi, którymi manipuluje, to kolejna ważna koncepcja w projektowaniu OO.

Istnieje wiele stylów kodowania, w których ta koncepcja owijania byłaby złym pomysłem - kto chce napisać całą klasę tylko podczas wdrażania jednorazowego skryptu lub testu? Ale myślę, że enkapsulacja wszystkich podstawowych typów / kolekcji, których nie możesz dodać do siebie kodu, jest obowiązkowa dla OO.

Bill K.
źródło
Ta odpowiedź jest niesamowita. Miałem ten problem (oczywiście), z pewną wątpliwością przeczytałem tę odpowiedź, wróciłem, spojrzałem na mój kod i zapytałem „jaki obiekt brakuje, który powinien mieć te metody”. Oto eleganckie rozwiązanie znalezione i wypełniona luka model-obiekt.
GreenAsJade
@Deduplicator W ciągu 40 lat programowania rzadko (nigdy?) Nie widziałem przypadku, w którym byłem blokowany przez zbyt wiele poziomów pośrednich (oprócz sporadycznej walki z moim IDE, aby przejść do implementacji zamiast interfejsu), ale „ często (bardzo często) widziałem przypadek, w którym ludzie pisali okropny kod zamiast tworzyć wyraźnie potrzebną klasę. Chociaż uważam, że zbyt wiele pośrednich ukąszeń mogło ukąsić niektórych ludzi, popełniłem błąd po stronie właściwych zasad OO, takich jak każda klasa, która robi jedną rzecz dobrze, właściwe powstrzymywanie itp.
Bill K
@BillK Ogólnie dobry pomysł. Ale bez włazu awaryjnego hermetyzacja może naprawdę przeszkodzić. I jest trochę piękna w darmowych algorytmach, które można dopasować do dowolnego typu, który chcesz, chociaż wprawdzie w językach ściśle-OO wszystko oprócz OO jest dość niezręczne.
Deduplicator
@Deduplicator Pisząc „narzędzie” Funkcje, które muszą być napisane poza określonymi wymaganiami biznesowymi, są poprawne. Klasy narzędzi pełne funkcji (takie jak kolekcje) są po prostu niewygodne w OO, ponieważ nie są powiązane z danymi. są to „Escape Hatch”, z którego korzystają osoby, które nie czują się komfortowo z OO (dostawcy narzędzi muszą ich używać ze względu na bardzo ogólną funkcjonalność). Moją sugestią powyżej było zawinięcie tych metod w klasy podczas pracy nad logiką biznesową, aby można było ponownie powiązać kod z danymi.
Bill K
1

Można użyć wzoru Provider i wstrzykiwać FooSomethingz FooUtilProviderobiektu (może być w konstruktorze; tylko raz).

FooUtilProvidermiałby tylko jedną metodę: FooUtils get();. Klasa użyłaby wtedy dowolnej FooUtilsinstancji, którą udostępnia.

Teraz jest to jeden krok od korzystania z frameworku Dependency Injection, gdy trzeba tylko połączyć cointainer DI tylko dwa razy: w przypadku kodu produkcyjnego i pakietu testów. Po prostu wiążesz FooUtilsinterfejs z jednym RealFooUtilslub drugim MockedFooUtils, a reszta dzieje się automatycznie. Każdy obiekt, od którego zależy, FooUtilsProviderotrzymuje odpowiednią wersję FooUtils.

Konrad Morawski
źródło