Dlaczego używanie „finału” na zajęciach jest tak złe?

34

Refaktoryzuję starszą stronę PHP OOP.

Kusi mnie, aby zacząć używać „końcowego” na zajęciach do „ make it explicit that the class is currently not extended by anything”. Może to zaoszczędzić dużo czasu, jeśli przyjdę na zajęcia i zastanawiam się, czy mogę zmienić nazwę / usunąć / zmodyfikować protectedwłaściwość lub metodę. Jeśli naprawdę chcę rozszerzyć klasę, mogę po prostu usunąć ostatnie słowo kluczowe, aby odblokować je do rozszerzenia.

To znaczy, jeśli przyjdę na zajęcia, na których nie ma zajęć dla dzieci, mogę zapisać tę wiedzę, zaznaczając klasę na końcu. Następnym razem, gdy do niego dojdę, nie będę musiał ponownie przeszukiwać bazy kodu, aby sprawdzić, czy ma ona dzieci. W ten sposób oszczędzasz czas podczas refaktoryzacji.

To wszystko wydaje się rozsądnym pomysłem na oszczędzanie czasu ... ale często czytałem, że zajęcia należy kończyć tylko na rzadkie / specjalne okazje.

Może to psuje tworzenie fałszywych obiektów lub ma inne skutki uboczne, o których nie myślę.

czego mi brakuje?

JW01
źródło
3
Jeśli masz pełną kontrolę nad swoim kodem, to chyba nie jest straszny, ale trochę po stronie OCD. Co się stanie, jeśli opuścisz zajęcia (np. Nie ma dzieci, ale nie jest to ostateczne)? Lepiej pozwól narzędziu zeskanować kod i powiedzieć, czy dana klasa ma dzieci. Gdy tylko zapakujesz to jako bibliotekę i podasz komuś innemu, radzenie sobie z „finałem” stanie się bolesne.
Job
Na przykład MbUnit to otwarta platforma do testowania jednostek .Net, a MsTest nie jest (niespodzianka niespodzianka). W rezultacie MUSISZ wydać 5k na MSFT tylko dlatego, że chcesz uruchomić niektóre testy na MSFT. Inni testerzy nie mogą uruchomić dll testowej zbudowanej z MsTest - wszystko z powodu końcowych klas używanych przez MSFT.
Job
3
Kilka postów na blogu na ten temat: Klasy końcowe: Otwarte na rozszerzenie, Zamknięte dla dziedziczenia (maj 2014; autor: Mathias Verraes)
hakre
Niezły artykuł. Mówi o tym moje myśli. Nie wiedziałem o tych adnotacjach, więc było to przydatne. Twoje zdrowie.
JW01
„PHP 5 wprowadza słowo kluczowe final, które uniemożliwia klasom potomnym zastąpienie metody, poprzedzając definicję słowem final. Jeśli sama klasa jest definiowana jako końcowa, nie można jej rozszerzyć.” php.net/manual/en/language.oop5.final.php Wcale nie jest źle.
Czarny

Odpowiedzi:

54

Często czytałem, że zajęcia powinny być „ostateczne” tylko w rzadkich / specjalnych przypadkach.

Ktokolwiek to napisał, jest zły. Używaj finalswobodnie, nie ma w tym nic złego. Dokumentuje, że klasa nie została zaprojektowana z myślą o dziedziczeniu, i jest to zwykle prawdą domyślnie dla wszystkich klas: projektowanie klasy, którą można w sposób znaczący odziedziczyć, wymaga więcej niż tylko usunięcia finalspecyfikatora; wymaga dużo uwagi.

Dlatego używanie finaldomyślnie nie jest złe. W rzeczywistości wiele osób uważa, że ​​powinno to być domyślne, np. Jon Skeet .

Może to psuje tworzenie fałszywego obiektu…

Jest to rzeczywiście zastrzeżenie, ale zawsze możesz skorzystać z interfejsów, jeśli chcesz wyśmiewać swoje klasy. Jest to z pewnością lepsze niż udostępnienie wszystkich klas dziedziczeniu tylko w celu kpienia.

Konrad Rudolph
źródło
1
1+: Ponieważ to psuje drwiny; rozważ utworzenie interfejsów lub klas abstrakcyjnych dla każdej „ostatecznej” klasy.
Spoike,
Cóż, to stawia klucz w pracach. To późne przybycie zaprzecza wszystkim pozostałym odpowiedziom. Teraz nie wiem w co wierzyć: o. (Odznaczono moją zaakceptowaną odpowiedź i wstrzymałem decyzję podczas ponownego procesu)
JW01
1
Możesz wziąć pod uwagę sam interfejs API języka Java i częstotliwość korzystania ze słowa kluczowego „final”. W rzeczywistości nie jest używany bardzo często. Jest stosowany w przypadkach, w których wydajność może się pogorszyć z powodu dynamicznego wiązania, które ma miejsce, gdy wywoływane są metody „wirtualne” (w Javie wszystkie metody są wirtualne, jeśli nie są zadeklarowane jako „ostateczne” lub „prywatne”) lub podczas wprowadzania niestandardowego podklasy klasy API Java mogą powodować problemy ze spójnością, takie jak podklasa „java.lang.String”. Łańcuch Java jest obiektem wartości według definicji i nie może później zmieniać swojej wartości. Podklasa może złamać tę regułę.
Jonny Dee,
5
@Jonny Większość Java API jest źle zaprojektowana. Pamiętaj, że większość z nich ma ponad dziesięć lat i w międzyczasie opracowano wiele najlepszych praktyk. Ale nie wierz mi na słowo: sami inżynierowie Java tak mówią, przede wszystkim Josh Bloch, który szczegółowo o tym napisał, np. W Effective Java . Zapewniamy, że gdyby dziś opracowano API Java, wyglądałoby to zupełnie inaczej i finalodgrywało by znacznie większą rolę.
Konrad Rudolph
1
Nadal nie jestem przekonany, że częstsze stosowanie „ostatecznego” stało się najlepszą praktyką. Moim zdaniem, „ostateczny” powinien być naprawdę używany tylko wtedy, gdy wymagają tego wymagania wydajności lub jeśli musisz zapewnić spójność, której nie można złamać podklasami. We wszystkich innych przypadkach niezbędna dokumentacja powinna się znaleźć w dokumentacji (którą i tak należy napisać), a nie w kodzie źródłowym jako słowach kluczowych, które wymuszają określone wzorce użytkowania. Dla mnie to gra w boga. Lepszym rozwiązaniem byłoby użycie adnotacji. Można dodać adnotację „@final”, a kompilator może wydać ostrzeżenie.
Jonny Dee,
8

Jeśli chcesz zostawić sobie notatkę, że klasa nie ma podklas, to zrób to i użyj komentarza, po to jest. „Ostateczne” słowo kluczowe nie jest komentarzem, a używanie słów kluczowych w języku tylko w celu zasygnalizowania czegoś (i tylko Ty będziesz wiedział, co to znaczy), jest złym pomysłem.

Jeremy
źródło
17
To jest całkowicie fałszywe! „Używanie słów kluczowych w języku tylko w celu zasygnalizowania czegoś […] to zły pomysł.” - Przeciwnie, właśnie po to są słowa kluczowe w języku. Twoje wstępne zastrzeżenie „i tylko ty będziesz wiedział, co to znaczy”, jest oczywiście poprawne, ale nie ma tutaj zastosowania; PO stosuje finalprawidłowo. Nie ma w tym nic złego. A użycie funkcji języka w celu wymuszenia ograniczenia jest zawsze lepsze niż użycie komentarza.
Konrad Rudolph
8
@Konrad: Z wyjątkiem finaloznaczenia „Nigdy nie należy tworzyć podklasy tej klasy” (ze względów prawnych lub coś takiego), a nie „ta klasa obecnie nie ma dzieci, więc nadal mogę bezpiecznie zadzierać z jej chronionymi członkami”. Intencją tego finaljest antyteza „swobodnie edytowalnego”, a finalklasa nie powinna mieć nawet żadnych protectedczłonków!
SF.
3
@Konrad Rudolph To wcale nie jest maleńkie. Jeśli inne osoby chcą później rozszerzyć jego zajęcia, istnieje duża różnica między komentarzem z napisem „nie przedłużono” a słowem kluczowym z napisem „może się nie przedłużać”.
Jeremy
2
@Konrad Rudolph To brzmi jak świetna opinia, aby postawić pytanie dotyczące projektowania języka OOP. Ale wiemy, jak działa PHP, i przyjmuje inne podejście, pozwalając na rozszerzenie, o ile nie jest zamknięte. Udawanie, że oklaskiwanie słów kluczowych jest w porządku, ponieważ „tak właśnie powinno być”, jest tylko obroną.
Jeremy
3
@Jeremy Nie łączę słów kluczowych. Słowo kluczowe finaloznacza: „tej klasy nie można przedłużyć [na razie]”. Nic więcej, nic mniej. Czy PHP został zaprojektowany z myślą o tej filozofii nie ma znaczenia: to ma na finalsłowo kluczowe, po wszystkim. Po drugie, argumentowanie na podstawie projektu PHP na pewno się nie powiedzie, biorąc pod uwagę, jak patchworkowy i po prostu ogólnie źle zaprojektowany PHP.
Konrad Rudolph
5

Jest ładny artykuł na temat „Kiedy ogłaszać klasę jako ostateczną” . Kilka cytatów z tego:

TL; DR: Twórz klasy zawsze final, jeśli implementują interfejs i nie są zdefiniowane żadne inne metody publiczne

Dlaczego muszę używać final?

  1. Zapobieganie masowemu spadkowi łańcucha losu
  2. Zachęcająca kompozycja
  3. Zmuś programistę do myślenia o publicznym interfejsie API użytkownika
  4. Zmuś programistę do zmniejszenia publicznego interfejsu API obiektu
  5. finalKlasa może być zawsze rozciągliwa
  6. extends przerywa enkapsulację
  7. Nie potrzebujesz tej elastyczności
  8. Możesz zmienić kod

Kiedy unikać final:

Klasy końcowe działają skutecznie tylko przy następujących założeniach:

  1. Istnieje abstrakcja (interfejs), którą implementuje ostatnia klasa
  2. Cały publiczny interfejs API ostatecznej klasy jest częścią tego interfejsu

Jeśli brakuje jednego z tych dwóch warunków wstępnych, prawdopodobnie osiągniesz moment, w którym sprawisz, że klasa będzie rozszerzalna, ponieważ twój kod tak naprawdę nie opiera się na abstrakcjach.

PS Dzięki @ocramius za wspaniałą lekturę!

Nikita U.
źródło
5

„końcowy” dla klasy oznacza: Chcesz podklasę? Śmiało, usuń „ostateczną” podklasę, ile chcesz, ale nie narzekaj, jeśli to nie działa. Jesteś sam.

Gdy klasę można zaklasyfikować do podklasy, jej zachowanie, na którym polegają inni, należy opisać abstrakcyjnie, zgodnie z podklasami. Dzwoniący muszą być napisani, aby oczekiwać pewnej zmienności. Dokumentacja musi być starannie napisana; nie możesz powiedzieć ludziom, żeby „patrzyli na kod źródłowy”, ponieważ kodu źródłowego jeszcze nie ma. To wszystko wysiłek. Jeśli nie oczekuję, że klasa jest podklasowana, to jest to niepotrzebny wysiłek. „końcowy” wyraźnie mówi, że wysiłek ten nie został podjęty i daje uczciwe ostrzeżenie.

gnasher729
źródło
-1

Jedną z rzeczy, o których być może nie pomyślałeś, jest fakt, że DOWOLNA zmiana klasy oznacza, że ​​musi ona przejść nowe testy jakości.

Nie oznaczaj rzeczy jako ostatecznych, chyba że naprawdę, naprawdę masz na myśli.


źródło
Dzięki. Czy możesz to wyjaśnić dalej. Czy masz na myśli to, że jeśli oznaczę klasę jako final(zmianę), to muszę ją ponownie przetestować?
JW01
4
Powiedziałbym to mocniej. Nie oznaczaj rzeczy jako ostatecznych, chyba że nie można wprowadzić rozszerzenia do działania z powodu projektu klasy.
S.Lott,
Dzięki S.Lott - Staram się zrozumieć, jak złe użycie finału wpływa na kod / projekt. Czy doświadczyłeś jakichś złych horrorów, które doprowadziły cię do tak dogmatycznego nastawienia do oszczędnego używania final. Czy to doświadczenie z pierwszej ręki?
JW01
1
@ JW01: finalKlasa ma jeden podstawowy przypadek użycia. Masz klasy polimorficzne, których nie chcesz rozszerzać, ponieważ podklasa może złamać polimorfizm. Nie używaj, finalchyba że musisz zapobiec tworzeniu podklas. Poza tym jest bezużyteczny.
S.Lott,
@ JW01, w warunkach produkcyjnych może nie być DOZWOLONE usunąć ostateczna, ponieważ to nie jest kod przetestowane i zatwierdzone przez klienta. Możesz jednak mieć możliwość dodania dodatkowego kodu (do testowania), ale możesz nie być w stanie zrobić tego, co chcesz, ponieważ nie możesz rozszerzyć swojej klasy.
-1

Użycie „ostatecznego” zabiera swobodę innym, którzy chcą korzystać z twojego kodu.

Jeśli kod, który piszesz, jest tylko dla ciebie i nigdy nie zostanie udostępniony publicznie ani klientowi, możesz oczywiście zrobić z tym kodem, co chcesz. W przeciwnym razie uniemożliwisz innym korzystanie z Twojego kodu. Zbyt często musiałem pracować z interfejsem API, który byłby łatwy do rozszerzenia dla moich potrzeb, ale wtedy przeszkadzało mi „końcowe”.

Ponadto często istnieje kod, którego lepiej nie tworzyć private, ale protected. Jasne, privateoznacza „enkapsulację” i ukrywanie rzeczy uważanych za szczegóły implementacji. Ale jako programista API równie dobrze mogę udokumentować fakt, że metoda xyzjest uważana za szczegół implementacji, a zatem może zostać zmieniona / usunięta w przyszłej wersji. Tak więc każdy, kto pomimo ostrzeżenia będzie polegał na takim kodzie, robi to na własne ryzyko. Ale może to zrobić i ponownie wykorzystać (mam nadzieję, że już przetestowany) kod i szybciej znaleźć rozwiązanie.

Oczywiście, jeśli implementacja API jest open source, można po prostu usunąć „końcowy” lub uczynić metody „chronionymi”, ale następnie zmieniłeś kod i musisz śledzić zmiany w postaci łatek.

Jeśli jednak implementacja jest zamkniętym źródłem, nie możesz znaleźć obejścia lub, w najgorszym przypadku, przejść na inny interfejs API z mniejszymi ograniczeniami dotyczącymi możliwości dostosowywania / rozszerzenia.

Zauważ, że nie uważam, że „ostateczny” lub „prywatny” jest zły, ale myślę, że są po prostu zbyt często używane, ponieważ programista nie pomyślał o swoim kodzie pod względem ponownego użycia i rozszerzenia kodu.

Jonny Dee
źródło
2
„Dziedziczenie nie jest ponownym użyciem kodu”.
quant_dev
2
Ok, jeśli nalegasz na idealną definicję, masz rację. Dziedziczenie wyraża relację „jest-a” i jest to fakt, który pozwala na polimorfizm i zapewnia sposób rozszerzenia interfejsu API. Ale w praktyce dziedziczenie jest bardzo często wykorzystywane do ponownego wykorzystania kodu. Dotyczy to zwłaszcza wielokrotnego dziedziczenia. A gdy tylko wywołasz kod superklasy, faktycznie ponownie go użyjesz.
Jonny Dee,
@quant_dev: Ponowne użycie w OOP oznacza przede wszystkim polimorfię. Dziedziczenie jest punktem krytycznym dla zachowania polimorficznego (współdzielenie interfejsu).
Falcon
@Falcon Interfejs udostępniania! = Udostępnianie kodu. Przynajmniej nie w zwykłym tego słowa znaczeniu.
quant_dev
3
@quant_dev: Nieprawidłowe użycie wielokrotne w sensie OOP. Oznacza to, że jedna metoda może działać na wielu typach danych dzięki współużytkowaniu interfejsu. To główna część ponownego użycia kodu OOP. Dziedziczenie automatycznie pozwala nam współdzielić interfejsy, co znacznie poprawia możliwość ponownego użycia kodu. Dlatego mówimy o możliwości ponownego użycia w OOP. Po prostu tworzenie bibliotek z procedurami jest prawie tak stare jak samo programowanie i można to zrobić w prawie każdym języku, jest to powszechne. Ponowne użycie kodu w OOP to coś więcej.
Falcon