Klasy użytkowe są złe? [Zamknięte]


98

Widziałem ten wątek

Jeśli klasa „Utilities” jest zła, gdzie mam umieścić swój ogólny kod?

i pomyślał, dlaczego klasy użytkowe są złe?

Powiedzmy, że mam model domeny, który obejmuje dziesiątki klas. Muszę mieć możliwość obsługi instancji xml-ify. Czy utworzyć metodę toXml na rodzicu? Czy mogę utworzyć klasę pomocniczą MyDomainXmlUtility.toXml? Jest to przypadek, w którym potrzeba biznesowa obejmuje cały model domeny - czy naprawdę należy do metody instancji? A co, jeśli istnieje kilka metod pomocniczych dotyczących funkcjonalności XML aplikacji?


27
Dewaluacja terminu „zło” jest złem!
Matthew Lock,

1
@matthew i zachowałem warunki postu, na którym opiera się moje pytanie ...;)
hvgotcodes

Klasy użyteczności są złym pomysłem z tych samych powodów, dla których są to single.
CurtainDog,

1
Debata na temat tego, czy mieć metodę toXML, koncentruje się na modelach domen bogatych i anemicznych. codeflow.blogspot.com/2007/05/anemic-vs-rich-domain-models.html
James P.

@james, toXML to tylko przykład ... a co z niektórymi funkcjami regex, które są używane wszędzie? Na przykład musisz zrobić kilka rzeczy z ciągami w modelu domeny, ale nie możesz użyć podklasy z powodu innego nadrzędnego problemu, który używał Twojej jednej nadklasy (w java)
hvgotcodes

Odpowiedzi:


130

Klasy użytkowe nie są dokładnie złe, ale mogą naruszać zasady, które składają się na dobry projekt zorientowany obiektowo. W dobrym projekcie obiektowym większość klas powinna reprezentować jedną rzecz oraz wszystkie jej atrybuty i operacje. Jeśli operujesz na jakiejś rzeczy, ta metoda powinna prawdopodobnie należeć do tej rzeczy.

Czasami jednak można użyć klas narzędziowych do zgrupowania kilku metod razem - przykładem jest java.util.Collectionsklasa udostępniająca szereg narzędzi, których można użyć w dowolnej kolekcji Java. Nie są one specyficzne dla jednego konkretnego typu kolekcji, ale zamiast tego implementują algorytmy, które mogą być używane w dowolnej kolekcji.

Naprawdę, to, co musisz zrobić, to przemyśleć swój projekt i określić, gdzie najlepiej jest umieścić metody. Zwykle jest to operacje wewnątrz klasy. Jednak czasami jest to rzeczywiście klasa użytkowa. Jednak gdy używasz klasy narzędziowej, nie wrzucaj do niej tylko losowych metod, zamiast tego organizuj metody według przeznaczenia i funkcjonalności.


krawaty w dobrze z tym artykułem o sprawdzenie klasy narzędzie / pomocnika przed stałymi zasadzie oo oraz readability.com/articles/rk1vvcqy
melaos

8
Jeśli język nie oferuje żadnego mechanizmu przestrzeni nazw poza klasami, nie masz innego wyjścia, jak tylko nadużywać klasy, w której zrobiłaby to przestrzeń nazw. W C ++ możesz umieścić wolnostojącą funkcję, niezwiązaną z innymi funkcjami, w przestrzeni nazw. W Javie musisz umieścić go w klasie jako statyczny (klasa) element członkowski.
Unslander Monica

1
Grupowanie jako metody może być kanoniczne z punktu widzenia OOP. Jednak OOP rzadko jest rozwiązaniem (wśród wyjątków jest architektura wysokiego poziomu i zestawy narzędzi widżetów jako jeden z przypadków, w których niewiarygodnie zepsuta semantyka ponownego wykorzystania kodu przez dziedziczenie faktycznie pasuje) i ogólnie metody zwiększają sprzężenie i zależności. Trywialny przykład: jeśli dostarczasz metody drukarki / skanera do swojej klasy jako metody, łączysz tę klasę z bibliotekami drukarki / skanera. Dodatkowo możesz ustalić liczbę możliwych implementacji na efektywną jedną. Chyba że dodasz interfejs i dalej zwiększysz zależności.
Jo So

Z drugiej strony zgadzam się z twoim zdaniem „grupuj według celu i funkcjonalności”. Wróćmy jeszcze raz do przykładu drukarki / skanera i zacznijmy od zestawu klas zgodnych, zaprojektowanych do wspólnego celu. Możesz napisać kod do debugowania i zaprojektować tekstową reprezentację swoich klas. Możesz zaimplementować swoje drukarki debugowania w jednym pliku, który zależy od wszystkich tych klas i biblioteki drukarek. Kod bez debugowania nie będzie obciążony zależnościami tej implementacji.
Jo So

1
Klasy Util i Helper są niefortunnym artefaktem takich ograniczeń, a ci, którzy nie rozumieją OOP lub nie rozumieją domeny problemowej, nadal piszą kod proceduralny.
bilelovitch

58

Myślę, że ogólny konsensus jest taki, że klasy użytkowe nie są złe same w sobie . Musisz tylko rozsądnie ich używać:

  • Zaprojektuj statyczne metody narzędziowe tak, aby były ogólne i wielokrotnego użytku. Upewnij się, że są bezpaństwowcami; tj. brak zmiennych statycznych.

  • Jeśli masz wiele metod narzędziowych, podziel je na klasy w sposób, który ułatwi programistom ich znalezienie.

  • Nie używaj klas narzędzi, w przypadku których lepszym rozwiązaniem byłyby metody statyczne lub instancyjne w klasie domeny. Na przykład zastanów się, czy lepszym rozwiązaniem byłyby metody w abstrakcyjnej klasie bazowej lub instancji klasy pomocniczej.

  • W przypadku języka Java 8 i nowszych „metody domyślne” w interfejsie mogą być lepszą opcją niż klasy narzędziowe. (Zobacz na przykład Interfejs z metodami domyślnymi a Klasa abstrakcyjna w języku Java 8 ).


Innym sposobem spojrzenia na to pytanie jest zauważenie, że w zacytowanym pytaniu „Jeśli klasy użyteczności są„ złe ”” jest argumentem słomnika. Tak jak ja pytam:

„Jeśli świnie potrafią latać, czy powinienem nosić parasolkę?”.

W powyższym pytaniu nie mówię właściwie, że świnie potrafią latać ... ani że zgadzam się z tezą, że potrafią latać.

Typowe stwierdzenia „xyz to zło” to narzędzia retoryczne, które mają skłonić cię do myślenia, przedstawiając skrajny punkt widzenia. Rzadko (jeśli w ogóle) są pomyślane jako stwierdzenia dosłownych faktów.


16

Klasy narzędziowe są problematyczne, ponieważ nie grupują obowiązków z danymi, które je obsługują.

Są jednak niezwykle przydatne i cały czas buduję je jako konstrukcje stałe lub jako odskocznię podczas dokładniejszego refaktora.

Z punktu widzenia czystego kodu klasy użytkowe naruszają pojedynczą odpowiedzialność i zasadę otwartego zamknięcia. Mają wiele powodów do zmiany i zgodnie z projektem nie można ich rozszerzać. Tak naprawdę powinny istnieć tylko podczas refaktoryzacji jako pośredni cruft.


2
Nazwałbym to problemem praktycznym, ponieważ np. W Javie nie można tworzyć funkcji, które nie są metodami w klasie. W takim języku „klasa bez zmiennych i tylko z metodami statycznymi” jest idiomem, który oznacza przestrzeń nazw funkcji użytkowych ... W obliczu takiej klasy w Javie, IMHO jest niepoprawne i bezcelowe jest mówienie o klasie , mimo że classsłowo kluczowe jest używane. Szarpie się jak przestrzeń nazw, chodzi jak przestrzeń nazw - to jedna ...
Unslander Monica

2
Przykro mi, że jestem szczery, ale „bezpaństwowe metody statyczne [...] dziwactwa w systemie OOP [...] filozoficzny problem” jest skrajnie błędne. To świetnie, jeśli możesz uniknąć stanu, ponieważ ułatwia to pisanie poprawnego kodu. To jest ZŁAMANE, gdy muszę pisać, x.equals(y)ponieważ 1) fakt, że to naprawdę nie powinno modyfikować żadnego stanu, nie jest przekazywany (i nie mogę na tym polegać, nie znając implementacji) 2) Nigdy nie zamierzałem wstawiać x" faworyzowane ”lub„ działające ”stanowisko w porównaniu z y3) Nie chcę brać pod uwagę„ tożsamości ” xlub yinteresują mnie jedynie ich wartości.
Jo So

4
Naprawdę większość funkcji w świecie rzeczywistym najlepiej wyraża się jako statyczne, bezpaństwowe funkcje matematyczne. Czy Math.PI jest obiektem, który powinien zostać zmutowany? Czy naprawdę muszę utworzyć wystąpienie AbstractSineCalculator, który implementuje IAbstractRealOperation tylko po to, aby uzyskać sinus z liczby?
Jo So,

1
@JoSo Całkowicie zgadzam się co do wartości bezpaństwowości i bezpośrednich obliczeń. Naiwny OO jest filozoficznie przeciwny temu i myślę, że jest to głęboko wadliwe. Zachęca do abstrakcji rzeczy, które jej nie potrzebują (jak wykazałeś) i nie dostarcza znaczących abstrakcji dla rzeczywistych obliczeń. Jednak zrównoważone podejście do używania języka OO zmierza w kierunku niezmienności i bezpaństwowości, ponieważ promują modułowość i łatwość utrzymania.
Alain O'Dea

2
@ AlainO'Dea: Zdaję sobie sprawę, że źle odczytałem Twój komentarz jako sprzeciwiający się funkcjonalności bezpaństwowej. Przepraszam za mój ton - jestem obecnie zaangażowany w mój pierwszy projekt Java (po tym, jak unikałem OOP przez większą część mojego 10 lat programowania) i jestem pochowany pod warstwami abstrakcyjnych rzeczy o niejasnych stanach i znaczeniach. A ja potrzebuję tylko świeżego powietrza :-).
Jo So

10

Przypuszczam, że kiedy zaczyna się robić zło

1) Robi się zbyt duży (w tym przypadku po prostu pogrupuj je w znaczące kategorie).
2) Istnieją metody, które nie powinny być metodami statycznymi

Ale dopóki te warunki nie zostaną spełnione, myślę, że są bardzo przydatne.


„Istnieją metody, które nie powinny być metodami statycznymi”. Jak to jest możliwe?
Koray Tugay

@KorayTugay Rozważmy niestatyczne Widget.compareTo(Widget widget)i statyczne WidgetUtils.compare(Widget widgetOne, Widget widgetTwo). Porównanie jest przykładem czegoś, czego nie powinno się robić statycznie.
ndm13

6

Praktyczna zasada

Możesz spojrzeć na ten problem z dwóch perspektyw:

  • Ogólne *Utilmetody są często sugestią złego projektu kodu lub leniwej konwencji nazewnictwa.
  • Jest to uzasadnione rozwiązanie projektowe dla bezstanowych funkcji wielokrotnego użytku między domenami. Należy pamiętać, że dla prawie wszystkich typowych problemów istnieją rozwiązania.

Przykład 1. Prawidłowe wykorzystanie utilklas / modułów. Przykład biblioteki zewnętrznej

Załóżmy, że piszesz aplikację obsługującą pożyczki i karty kredytowe. Dane z tych modułów są udostępniane za pośrednictwem usług internetowych w jsonformacie. Teoretycznie można ręcznie konwertować obiekty na łańcuchy, które będą w jsonśrodku, ale to wymyśliłoby koło na nowo. Prawidłowym rozwiązaniem byłoby zawarcie w obu modułach zewnętrznej biblioteki służącej do konwersji obiektów java do żądanego formatu. (na przykładowym obrazie pokazałem gson )

wprowadź opis obrazu tutaj


Przykład 2. Prawidłowe wykorzystanie utilklas / modułów. Pisanie własnych utilbez wymówek do innych członków zespołu

Jako przypadek użycia przyjmijmy, że musimy wykonać jakieś obliczenia w dwóch modułach aplikacji, ale oba muszą wiedzieć, kiedy w Polsce są święta. Teoretycznie możesz wykonać te obliczenia wewnątrz modułów, ale lepiej byłoby wyodrębnić tę funkcjonalność do oddzielnego modułu.

Oto mały, ale ważny szczegół. Klasa / moduł, który napisałeś, nie nazywa się HolidayUtil, ale PolishHolidayCalculator. Funkcjonalnie jest to utilklasa, ale udało nam się uniknąć ogólnego słowa.

wprowadź opis obrazu tutaj


1
Widzę wieloletni fan;) Niesamowita aplikacja.
Sridhar Sarnobat

3

Klasy użytkowe są złe, ponieważ oznaczają, że byłeś zbyt leniwy, aby wymyślić lepszą nazwę dla klasy :)

Biorąc to pod uwagę, jestem leniwy. Czasami po prostu musisz wykonać swoją pracę, a twój umysł jest pusty… wtedy zaczynają wkradać się zajęcia z "narzędzi".


3

Patrząc teraz wstecz na to pytanie, powiedziałbym, że metody rozszerzające C # całkowicie niszczą potrzebę klas narzędziowych. Ale nie wszystkie języki mają tak genialny konstrukt.

Masz również JavaScript, w którym możesz po prostu dodać nową funkcję bezpośrednio do istniejącego obiektu.

Ale nie jestem pewien, czy naprawdę istnieje elegancki sposób rozwiązania tego problemu w starszym języku, takim jak C ++ ...

Dobry kod obiektowy jest trochę trudny do napisania i trudno go znaleźć, ponieważ pisanie dobrego obiektu obiektowego wymaga dużo więcej czasu / wiedzy niż pisanie przyzwoitego kodu funkcjonalnego.

A kiedy masz ograniczony budżet, twój szef nie zawsze jest zadowolony, widząc, że spędziłeś cały dzień na pisaniu kilku zajęć ...


3

Nie do końca zgadzam się, że klasy użytkowe są złe.

Chociaż klasa użytkowa może w pewien sposób naruszać zasady OO, nie zawsze są one złe.

Na przykład wyobraź sobie, że potrzebujesz funkcji, która wyczyści ciąg ze wszystkich podciągów pasujących do wartości x.

stl c ++ (na razie) nie obsługuje tego bezpośrednio.

Możesz utworzyć polimorficzne rozszerzenie std::string.

Problem w tym, czy naprawdę chcesz, aby KAŻDY łańcuch, którego używasz w swoim projekcie, był Twoją rozszerzoną klasą ciągów?

Są chwile, kiedy OO tak naprawdę nie ma sensu, a to jest jeden z nich. Chcemy, aby nasz program był kompatybilny z innymi programami, więc będziemy się trzymać std::stringi tworzyć klasę StringUtil_(lub coś).

Powiedziałbym, że najlepiej jest trzymać się jednego narzędzia na klasę. Powiedziałbym, że głupie jest mieć jedno narzędzie dla wszystkich klas lub wiele narzędzi dla jednej klasy.


2

Bardzo łatwo jest oznaczyć coś jako narzędzie tylko dlatego, że projektant nie mógł wymyślić odpowiedniego miejsca na umieszczenie kodu. Często jest kilka prawdziwych „narzędzi”.

Zasadniczo zazwyczaj przechowuję kod w pakiecie, w którym jest używany po raz pierwszy, a następnie przekształcam go w bardziej ogólne miejsce tylko wtedy, gdy stwierdzę, że później jest naprawdę potrzebny gdzie indziej. Jedynym wyjątkiem jest sytuacja, gdy mam już pakiet, który wykonuje podobną / powiązaną funkcjonalność, a kod najlepiej tam pasuje.


2

Klasy narzędzi zawierające statyczne metody bezstanowe mogą być przydatne. Testy jednostkowe są często bardzo łatwe.



1

Większość klas Util jest zła, ponieważ:

  1. Poszerzają zakres metod. Udostępniają publicznie kod, który w innym przypadku byłby prywatny. Jeśli metoda util jest potrzebna wielu wywołującym w oddzielnych klasach i jest stabilna (tj. Nie wymaga aktualizacji), moim zdaniem lepiej jest skopiować i wkleić prywatne metody pomocnicze do klasy wywołującej. Po ujawnieniu go jako API, utrudniasz zrozumienie, jaki jest publiczny punkt wejścia do komponentu jar (utrzymujesz strukturę drzewa zwaną hierarchią z jednym rodzicem na metodę. Jest to łatwiejsze do rozdzielenia mentalnego na komponenty, co jest trudniejsze, gdy masz metody wywoływane z wielu metod nadrzędnych).
  2. Powodują martwy kod. Metody użytkowe z czasem stają się nieużywane, gdy Twoja aplikacja ewoluuje, a nieużywany kod zanieczyszcza bazę kodu. Gdyby pozostał prywatny, kompilator poinformowałby Cię, że metoda jest nieużywana i możesz ją po prostu usunąć (najlepszy kod nie zawiera żadnego kodu). Gdy sprawisz, że taka metoda stanie się nieprywatna, komputer nie będzie w stanie pomóc Ci usunąć nieużywanego kodu. Można go wywołać z innego pliku jar dla wszystkich znanych komputerów.

Istnieją pewne analogie do bibliotek statycznych i dynamicznych.


0

Kiedy nie mogę dodać metody do klasy (powiedzmy, Accountjest zablokowana przed zmianami przez Jr. Developers), po prostu dodaję kilka statycznych metod do mojej klasy Utilities, takich jak:

public static int method01_Account(Object o, String... args) {
    Account acc = (Account)o;
    ...
    return acc.getInt();
}  

1
Wygląda na to, że to ty jesteś dla mnie tym Jr ..: P Niezłomnym sposobem na to jest grzeczne poproszenie swojego „Jr Developer” o odblokowanie Accountpliku. Lub lepiej, wybierz nieblokujące się systemy kontroli źródła.
Sudeep

1
Zakładam, że miał na myśli, że Accountplik został zablokowany w taki sposób, że programiści Jr. nie mogą wprowadzać w nim zmian. Jako młodszy programista, aby to obejść, zrobił to, co powyżej. To powiedziawszy, nadal lepiej jest po prostu uzyskać dostęp do zapisu do pliku i zrobić to poprawnie.
Doc

Dodanie +1 do tego, ponieważ głosy przeciw wydają się szorstkie, gdy nikt nie ma pełnego kontekstu, dlaczego
dałeś

0

Klasy użytkowe nie zawsze są złe. Ale powinny zawierać tylko metody, które są wspólne dla szerokiego zakresu funkcji. Jeśli istnieją metody, których można używać tylko w ograniczonej liczbie klas, rozważ utworzenie klasy abstrakcyjnej jako wspólnego rodzica i umieść w niej metody.

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.