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ę FooUtil
wszędzie, ponieważ testowanie.
- Nie mogę uczynić
FooUtil
metod statycznymi, ponieważ nie będę w stanie wyśmiewać ich do testowania obuFooUtil
klas klienta. - Nie mogę utworzyć instancji
FooUtil
w 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 INSTANCE
element singletonu lub getInstance()
metodę statyczną , zachowując możliwość aktualizacji podstawowego pola statycznego klasy do testowania. Nie wydaje się też super-czysty.
FooUtil
metody powinny zostać wprowadzoneFooSomething
, może istnieją właściwości,FooSomething
które należy zmienić / rozszerzyć, abyFooUtil
metody nie były już potrzebne. Podaj nam bardziej konkretny przykład tego, coFooSomething
pomoże nam udzielić dobrej odpowiedzi na te pytania.Odpowiedzi:
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.
źródło
Można użyć wzoru Provider i wstrzykiwać
FooSomething
zFooUtilProvider
obiektu (może być w konstruktorze; tylko raz).FooUtilProvider
miałby tylko jedną metodę:FooUtils get();
. Klasa użyłaby wtedy dowolnejFooUtils
instancji, 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
FooUtils
interfejs z jednymRealFooUtils
lub drugimMockedFooUtils
, a reszta dzieje się automatycznie. Każdy obiekt, od którego zależy,FooUtilsProvider
otrzymuje odpowiednią wersjęFooUtils
.źródło