Jestem programistą od dawna (mam 49 lat), ale raczej nowością w programowaniu obiektowym. Czytałem o OO od czasu Eiffla Bertranda Meyera, ale zrobiłem naprawdę niewiele programowania OO.
Chodzi o to, że każda książka na temat projektowania OO zaczyna się od przykładu łodzi, samochodu lub innego wspólnego obiektu, którego często używamy, i zaczynają dodawać atrybuty i metody oraz wyjaśniać, w jaki sposób modelują stan obiektu i co można zrobić za pomocą to.
Zwykle mówią coś w stylu „im lepszy model, tym lepiej reprezentuje obiekt w aplikacji i tym lepiej wychodzi”.
Jak dotąd tak dobrze, ale z drugiej strony znalazłem kilku autorów, którzy podają takie przepisy, jak: „klasa powinna zmieścić się tylko na jednej stronie” (dodałbym „jaki rozmiar monitora?”, Skoro nie próbujemy wydrukować kod!).
Weźmy na przykład PurchaseOrder
klasę, która ma skończoną maszynę stanu kontrolującą jej zachowanie, i kolekcję PurchaseOrderItem
, jednym z argumentów w pracy jest to, że powinniśmy użyć PurchaseOrder
prostej klasy z pewnymi metodami (niewiele więcej niż klasa danych) i mieć PurchaseOrderFSM
„klasy ekspert”, który obsługuje skończona maszyna stanu ds PurchaseOrder
.
Powiedziałbym, że mieści się w klasyfikacji „Zazdrosna cecha” lub „Niewłaściwa intymność” postu Code Smells Jeffa Atwooda w Coding Horror. Po prostu nazwałbym to zdrowym rozsądkiem. Jeśli mogę wydać, zatwierdzić lub anulować moje zamówienie rzeczywistym zakupu, wówczas PurchaseOrder
klasa powinna mieć issuePO
, approvePO
i cancelPO
metod.
Czy to nie idzie w parze z „maksymalizacją spójności” i „minimalizowaniem łączenia” starych zasad, które rozumiem jako fundamenty OO?
Poza tym, czy to nie pomaga w utrzymaniu klasy?
źródło
PurchaseOrder
, można po prostu wymienić swoje metodyissue
,approve
orazcancel
.PO
Przyrostek dodaje tylko wartość ujemną.Odpowiedzi:
Klasa powinna stosować zasadę pojedynczej odpowiedzialności. Większość bardzo dużych klas, które widziałem, robi wiele rzeczy, dlatego są one zbyt duże. Spójrz na każdą metodę i kod decyduje, czy powinien on należeć do tej klasy, czy osobny, duplikat kodu jest wskazówką. Być może masz metodę issuePO, ale czy zawiera ona na przykład 10 linii kodu dostępu do danych? Ten kod prawdopodobnie nie powinien tam być.
źródło
Moim zdaniem rozmiar klasy nie ma znaczenia, o ile zmienne, funkcje i metody są odpowiednie dla danej klasy.
źródło
Największy problem z dużymi klasami dotyczy testowania tych klas lub ich interakcji z innymi klasami. Duża klasa sama w sobie nie stanowi problemu, ale jak wszystkie zapachy, wskazuje na potencjalny problem. Ogólnie rzecz biorąc, gdy klasa jest duża, oznacza to, że klasa robi więcej niż jedna klasa powinna.
Dla analogii samochodu, jeśli klasa
car
jest zbyt duża, prawdopodobnie oznacza to, że należy ją podzielić na klasyengine
, klasydoor
i klasywindshield
itp.źródło
Nie sądzę, aby istniała konkretna definicja zbyt dużej klasy. Korzystałbym jednak z następujących wskazówek, aby ustalić, czy powinienem przeredagować moją klasę, czy ponownie zdefiniować jej zakres:
W odniesieniu do 1 wyobraź sobie, że określasz rozmiar przedniej szyby, rozmiar koła, model żarówki tylnej i 100 innych szczegółów w swojej klasie
car
. Chociaż wszystkie są „odpowiednie” do samochodu, bardzo trudno jest prześledzić zachowanie takiej klasycar
. A kiedy pewnego dnia Twój kolega przypadkowo (lub, w niektórych przypadkach, celowo) zmieni rozmiar szyby przedniej w oparciu o model żarówki tylnej, to na zawsze dowiesz się, dlaczego szyba nie pasuje już do Twojego samochodu.W odniesieniu do 2 wyobraź sobie, że próbujesz zdefiniować wszystkie zachowania, jakie może mieć samochód w klasie
car
, alecar::open_roof()
będzie on dostępny tylko dla kabrioletów icar::open_rear_door()
nie dotyczy tych samochodów 2-drzwiowych. Chociaż samochody kabrioletowe i 2-drzwiowe są z definicji „samochodami”, wdrożenie ich w klasiecar
komplikuje klasę. Klasa staje się trudniejsza w użyciu i trudniejsza w utrzymaniu.Oprócz tych 2 wytycznych zasugerowałbym jasną definicję zakresu klasy. Po zdefiniowaniu zakresu zaimplementuj klasę ściśle na podstawie definicji. Przez większość czasu ludzie otrzymują „zbyt dużą” klasę z powodu złej definicji zakresu lub z powodu dowolnych funkcji dodanych do klasy
źródło
Powiedz, czego potrzebujesz w 300 liniach
Uwaga: ta ogólna zasada zakłada, że stosujesz podejście „jedna klasa na plik”, które samo w sobie jest dobrym pomysłem na łatwość utrzymania
To nie jest absolutna zasada, ale jeśli widzę ponad 200 linii kodu w jednej klasie, jestem podejrzliwy. Włącza się 300 linii i dzwonków ostrzegawczych. Kiedy otwieram czyjś kod i znajduję 2000 wierszy, wiem, że to czas refaktoryzacji, nawet nie czytając pierwszej strony.
Sprowadza się to głównie do łatwości konserwacji. Jeśli Twój sprzeciw jest tak złożony, że nie możesz wyrazić jego zachowania w 300 liniach, zrozumienie go przez kogoś innego będzie bardzo trudne.
Przekonuję się, że naginam tę regułę w Modelu -> ViewModel -> Przeglądaj świat, w którym tworzę „obiekt zachowania” dla strony interfejsu użytkownika, ponieważ opisywanie pełnej serii zachowań na stronie interfejsu użytkownika zajmuje często ponad 300 linii Strona. Nadal jednak jestem nowy w tym modelu programowania i znajduję dobre sposoby na refaktoryzację / ponowne wykorzystywanie zachowań między stronami / modelami wyświetlania, co stopniowo zmniejsza rozmiar moich modeli ViewModels.
źródło
Przejdźmy do tego wstecz:
W rzeczywistości jest to kamień węgielny programowania, którego OO jest tylko jednym paradygmatem.
Pozwolę Antoine de Saint-Exupéry odpowiedzieć na twoje pytanie (bo jestem leniwy;)):
Im prostsza klasa, tym większa pewność, że masz rację, tym łatwiej będzie w pełni ją przetestować.
źródło
Jestem z OP w klasyfikacji Feature Envy. Nic nie irytuje mnie bardziej niż posiadanie szeregu klas z wieloma metodami, które działają na elementach wewnętrznych innych klas. OO sugeruje, aby modelować dane i operacje na tych danych. Nie oznacza to, że umieścisz operacje w innym miejscu niż dane! Stara się zebrać dane i te metody razem .
Przy tak powszechnych modelach
car
iperson
byłoby to jak wprowadzenie kodu do wykonania połączenia elektrycznego, a nawet zakręcenie rozrusznika, wperson
klasie. Mam nadzieję, że byłoby to oczywiście złe dla każdego doświadczonego programisty.Naprawdę nie mam idealnej wielkości klasy lub metody, ale wolę, aby moje metody i funkcje były dopasowane na jednym ekranie, aby móc zobaczyć całość w jednym miejscu. (Mam Vima skonfigurowanego do otwierania 60-liniowego okna, które akurat znajduje się dokładnie wokół liczby linii na wydrukowanej stronie.) Są miejsca, w których nie ma to większego sensu, ale działa dla mnie. Lubię stosować się do tych samych wytycznych dotyczących definicji klas i starać się, aby moje pliki były dłuższe niż kilka „stron”. Coś ponad 300, 400 linii zaczyna się nieporęczne (głównie dlatego, że klasa zaczęła przyjmować zbyt wiele bezpośrednich obowiązków).
źródło
Gdy definicja klasy ma kilkaset metod, jest za duża.
źródło
W pełni zgadzam się z zastosowaniem zasady pojedynczej odpowiedzialności dla klas, ale jeśli jest to przestrzegane i skutkuje dużymi klasami, niech tak będzie. Rzeczywistym celem powinno być to, aby poszczególne metody i właściwości nie były zbyt duże, cykliczna złożoność powinna być tam wskazówką, a następnie może to prowadzić do wielu prywatnych metod, które zwiększą ogólną wielkość klasy, ale pozostawiają ją czytelną i testowalną na poziomie szczegółowym. Jeśli kod pozostaje czytelny, rozmiar nie ma znaczenia.
źródło
Wystawianie zamówienia jest często skomplikowanym procesem, który wymaga więcej niż tylko zamówienia. W takim przypadku utrzymanie logiki biznesowej w osobnej klasie i uproszczenie zamówienia jest prawdopodobnie dobrym rozwiązaniem. Ogólnie jednak masz rację - nie chcesz klas „struktury danych”. Na przykład
issue()
metoda klasy biznesowej „POManager” prawdopodobnie powinna wywołaćissue()
metodę zamówienia zakupu . Organizacja producentów może następnie ustawić swój status na „Wydane” i zanotować datę wydania, podczas gdy POManager może następnie wysłać powiadomienie na rachunki do zapłaty, aby poinformować ich, że może oczekiwać faktury, oraz kolejne powiadomienie o zapasach, aby wiedzieli, że coś nadchodzi i oczekiwana data.Każdy, kto popycha „reguły” dla metody / klasy, oczywiście nie spędził wystarczająco dużo czasu w prawdziwym świecie. Jak wspomnieli inni, często jest to wskaźnik refaktoryzacji, ale czasami (szczególnie w pracy typu „przetwarzanie danych”) naprawdę trzeba napisać klasę za pomocą jednej metody obejmującej ponad 500 linii. Nie rozłączaj się.
źródło
Jeśli chodzi o wielkość fizyczną klasy, zobaczenie dużej klasy jest oznaką zapachu kodu, ale niekoniecznie oznacza, że zawsze są zapachy. (Zwykle tak jest). Jak ująłby to pirat z Piratów z Karaibów, jest to coś, co nazwalibyście „wytycznymi” niż rzeczywiste zasady. Próbowałem kiedyś ściśle przestrzegać „nie więcej niż stu LOC w klasie”, a rezultatem było mnóstwo niepotrzebnej nadmiernej inżynierii.
Zwykle rozpoczynam od tego swoje projekty. Co ta klasa robi w prawdziwym świecie? Jak moja klasa powinna odzwierciedlać ten obiekt zgodnie z jego wymaganiami? Dodajemy tutaj trochę stanu, pole tam, metodę pracy na tych polach, dodajemy jeszcze kilka i voila! Mamy klasę robotniczą. Teraz wypełnij podpisy odpowiednimi implementacjami, a my jesteśmy gotowi, tak przynajmniej myślisz. Teraz mamy ładną, dużą klasę ze wszystkimi potrzebnymi funkcjami. Problem polega jednak na tym, że nie bierze się pod uwagę sposobu działania prawdziwego świata. Rozumiem przez to, że nie uwzględnia „ZMIANY”.
Powodem, dla którego klasy są najlepiej dzielone na mniejsze części, jest to, że trudno jest później zmienić duże klasy bez wpływu na istniejącą logikę lub wprowadzenia nowego błędu lub dwóch przypadkowo lub po prostu utrudniając ponowne użycie wszystkiego. To tutaj pojawiają się refaktoryzacja, wzorce projektowe, SOLID itp., A efektem końcowym jest zwykle mała klasa pracująca na innych mniejszych, bardziej szczegółowych podklasach.
Również w twoim przykładzie dodanie IssuePO, ApprovePO i Cancel PO do klasy PurchaseOrder wydaje się nielogiczne. Zamówienia zakupu nie wydają się, nie zatwierdzają ani nie anulują. Jeśli PO powinien mieć metody, wówczas metody powinny działać na swoim stanie, a nie na klasie jako całości. To naprawdę tylko klasy danych / rekordów, nad którymi pracują inne klasy.
źródło
Wystarczająco duży, aby pomieścić całą logikę niezbędną do wykonania jego zadania.
Wystarczająco mały, aby można go było utrzymać i przestrzegać zasady pojedynczej odpowiedzialności .
źródło