Mam kilka ogromnych klas ponad 2k wierszy kodu (i ciągle rośnie), które chciałbym przebudować, jeśli to możliwe, aby mieć trochę więcej lekkiego i czystego projektu.
Powód, dla którego jest tak duży, to głównie dlatego, że klasy te obsługują zestaw map, do których większość metod potrzebuje dostępu, a metody są ze sobą bardzo powiązane.
Podam bardzo konkretny przykład: mam klasę o nazwie, Server
która obsługuje przychodzące wiadomości. Ma metod, takich jak joinChatroom
, searchUsers
, sendPrivateMessage
, itd. Wszystkie te metody manipulowania mapach takich jak users
, chatrooms
, servers
, ...
Może byłoby miło, gdybym mógł mieć klasę obsługującą wiadomości dotyczące czatów, inną zajmującą się użytkownikami itp., Ale głównym problemem tutaj jest to, że muszę korzystać ze wszystkich map w większości metod. Dlatego na razie wszyscy trzymają się Server
klasy, ponieważ wszyscy polegają na tych wspólnych mapach, a metody są ze sobą bardzo powiązane.
Musiałbym stworzyć czat klasy, ale z odniesieniem do każdego z pozostałych obiektów. Użytkownicy klasy ponownie z odniesieniem do wszystkich innych obiektów itp.
Czuję, że zrobiłbym coś złego.
źródło
Odpowiedzi:
Z twojego opisu zgaduję, że twoje mapy to czysta torba danych, z całą logiką
Server
metod. Przenosząc całą logikę pokoju rozmów do osobnej klasy, nadal utkniesz z mapami zawierającymi dane.Zamiast tego spróbuj modelować poszczególne pokoje rozmów, użytkowników itp. Jako obiekty. W ten sposób będziesz przekazywał tylko określone obiekty wymagane dla określonej metody, zamiast ogromnych map danych.
Na przykład:
Teraz łatwo jest wywołać kilka konkretnych metod obsługi wiadomości:
Chcesz dołączyć do pokoju rozmów?
Chcesz wysłać prywatną wiadomość?
Chcesz wysłać wiadomość publiczną?
źródło
Powinieneś być w stanie stworzyć klasę, która pomieści każdą kolekcję. Chociaż
Server
będzie potrzebował odniesienia do każdej z tych kolekcji, potrzebuje jedynie minimalnej ilości logiki, która nie będzie wymagała dostępu do podstawowych kolekcji ani zarządzania nimi. To sprawi, że bardziej oczywiste będzie dokładnie to, co robi Serwer, i rozdzieli, jak to robi.źródło
Kiedy zobaczyłem takie duże klasy, zauważyłem, że często jest tam klasa (lub więcej), która próbuje się wydostać. Jeśli znasz metodę, która Twoim zdaniem może nie być powiązana z tą klasą, ustaw ją w sposób statyczny. Kompilator poinformuje Cię o innych metodach wywoływanych przez tę metodę. Java będzie nalegać, aby były one również statyczne. Sprawiasz, że są statyczne. Ponownie kompilator powie ci o każdej metodzie, którą wywołuje. Robisz to w kółko, dopóki nie będziesz mieć więcej błędów kompilacji. Masz mnóstwo metod statycznych w swojej dużej klasie. Możesz teraz wyciągnąć je do nowej klasy i uczynić metodę niestatyczną. Następnie możesz nazwać tę nową klasę z oryginalnej dużej klasy (która powinna teraz zawierać mniej wierszy)
Następnie możesz powtarzać proces, aż będziesz zadowolony z projektu klasy.
Książka Martina Fowlera to naprawdę dobra lektura, więc polecam ją również, ponieważ czasami nie można użyć tej statycznej sztuczki.
źródło
Ponieważ większość kodu istnieje, sugeruję skorzystanie z klas pomocniczych w celu przeniesienia metod. Pomoże to w łatwym refaktoryzacji. Tak więc twoja klasa serwera nadal będzie zawierać mapy. Ale korzysta z klasy pomocniczej, powiedzmy ChatroomHelper z metodami takimi jak join (mapa czatów, użytkownik String), lista getUsers (mapa czatów), mapa getChatrooms (użytkownik String).
Klasa serwera przechowuje instancję ChatroomHelper, UserHelper itp., Przenosząc w ten sposób metody do swoich logicznych klas pomocniczych. Dzięki temu możesz pozostawić publiczne metody nienaruszone na serwerze, więc żaden rozmówca nie musi się zmieniać.
źródło
Aby dodać do wnikliwej odpowiedzi Casablanki - jeśli wiele klas musi robić te same podstawowe czynności z jakimś rodzajem bytu (dodawanie użytkowników do kolekcji, obsługa wiadomości itp.), Te procesy również powinny być oddzielone.
Można to zrobić na wiele sposobów - poprzez dziedziczenie lub kompozycję. Do dziedziczenia można użyć abstrakcyjnych klas bazowych z konkretnymi metodami, które zapewniają pola i funkcje do obsługi na przykład użytkowników lub wiadomości, a podmioty takie jak
chatroom
iuser
(lub dowolne inne) rozszerzają te klasy.Z różnych powodów dobrą ogólną zasadą jest używanie kompozycji zamiast dziedziczenia. Możesz użyć kompozycji, aby to zrobić na różne sposoby. Ponieważ obsługa użytkowników lub komunikatów jest funkcją kluczową dla klas, można argumentować, że wstrzyknięcie konstruktora jest najbardziej odpowiednie. W ten sposób zależność jest przezroczysta i nie można utworzyć obiektu bez wymaganej funkcjonalności. Jeśli sposób, w jaki użytkownicy lub wiadomości są obsługiwane, może się zmienić lub zostać rozszerzony, należy rozważyć użycie czegoś takiego jak wzorzec strategii .
W obu przypadkach pamiętaj o kodowaniu w kierunku interfejsów, a nie konkretnych klas zapewniających elastyczność.
Biorąc to wszystko pod uwagę, zawsze bierz pod uwagę koszt dodatkowej złożoności podczas korzystania z takich wzorców - jeśli nie będziesz go potrzebować, nie koduj go. Jeśli wiesz, że prawdopodobnie nie zmienisz sposobu obsługi użytkowników / wiadomości, nie potrzebujesz strukturalnej złożoności wzorca strategii - ale aby rozdzielić obawy i uniknąć powtórzeń, nadal powinieneś rozwodzić się ze wspólną funkcjonalnością z konkretnych przypadków, które go używają - i jeśli nie istnieją nadrzędne powody przeciwne, skomponuj użytkowników takiej funkcji obsługi (czaty, użytkownicy) z obiektem, który obsługuje.
Więc by podsumować:
searchUsers
może znaleźć się w klasie kolekcji lub repozytorium / mapie tożsamości )źródło
Słuchaj, myślę, że twoje pytanie jest zbyt ogólne, aby na nie odpowiedzieć, ponieważ tak naprawdę nie mamy pełnego opisu problemu, więc oferowanie dobrego projektu przy tak małej wiedzy jest niemożliwe. Mogę na przykład odpowiedzieć na jedną z twoich obaw dotyczących możliwej bezcelowości lepszego projektowania.
Mówisz, że twoja klasa Server i twoja przyszła klasa Chatroom dzielą się danymi o użytkownikach, ale dane te powinny być inne. Serwer prawdopodobnie ma zestaw użytkowników połączony z nim, podczas gdy Chatroom, który sam należy do Serwera, ma inny zestaw użytkowników, podzestaw pierwszego zestawu, tylko użytkowników zalogowanych obecnie do określonego pokoju Chatroom.
To nie jest ta sama informacja, nawet jeśli typy danych są identyczne.
Dobry projekt ma wiele zalet.
Nie przeczytałem jeszcze wspomnianej książki Fowlera, ale przeczytałem inne rzeczy Folwer i poleciły mi ją osoby, którym ufam, więc czuję się na tyle swobodnie, by zgodzić się z innymi.
źródło
Konieczność dostępu do map nie usprawiedliwia mega-klasy. Musisz rozdzielić logikę na kilka klas, a każda klasa musi mieć metodę getMap, aby inne klasy mogły uzyskać dostęp do map.
źródło
Użyłbym tej samej odpowiedzi, którą podałem gdzie indziej: weźcie klasę monolityczną i podzielcie jej obowiązki na inne klasy. Zarówno DCI, jak i wzorzec odwiedzin zapewniają dobre opcje.
źródło
Pod względem wielkości oprogramowania dużą klasą jest torba. Istnieją nieograniczone dokumenty potwierdzające to oświadczenie. Dlaczego ? ponieważ duże klasy są trudniejsze do zrozumienia niż małe klasy i potrzeba więcej czasu na modyfikację. Co więcej, duże klasy są bardzo trudne podczas testów. Duże klasy są dla ciebie bardzo trudne, gdy chcesz ich ponownie użyć, ponieważ najprawdopodobniej zawierają niepożądane rzeczy.
źródło