Czy istnieje inne uzasadnienie użycia abstrakcyjnych klas / interfejsów w C ++ i Javie

13

Według Herb Suttera należy preferować abstrakcyjne interfejsy (wszystkie czyste funkcje wirtualne) niż abstrakcyjne klasy w C ++, aby oddzielić implementację tak dalece, jak to możliwe. Chociaż osobiście uważam tę zasadę za bardzo przydatną, ostatnio dołączyłem do zespołu z wieloma programistami Java i w kodzie Java ta wytyczna wydaje się nie istnieć. Funkcje i ich implementacje bardzo często znajdują się w klasach abstrakcyjnych. Więc źle zrozumiałem Herb Sutter, nawet w przypadku C ++, czy też istnieje ogólna różnica w korzystaniu z funkcji abstrakcyjnych w C ++ w porównaniu z Javą. Czy klasy abstrakcyjne z kodem implementacyjnym są bardziej sensowne w Javie niż w C ++, a jeśli tak, to dlaczego?

Jaskółka oknówka
źródło
1
Miałem pewne wątpliwości i wreszcie umieściłem to tutaj, ponieważ mogło to wynikać z pewnych zasad projektowania, których mi brakuje w java oo. Zatem nie chodzi tu o ogólną radę, ale o dobre i złe użycie języka
Martin
Interfejsy mają być czysto wirtualne. Idea klas abstrakcyjnych polega na tym, że są one częściowo zaimplementowane, a wypełnienie luk bez wypełniania niepotrzebnego kodu (na przykład, po co zapisywać (bajt) i pisać (int)) w każdej podklasie, zależy od implementacji abstrakcyjne wywołanie klasy write (bajt) z write (int))
1
Możliwe powiązanie: stackoverflow.com/q/1231985/484230 podaje powód preferowania klas abstrakcyjnych w Javie. W przypadku C ++ ten powód nie wydaje się prawdą ze względu na istnienie bezpłatnych funkcji, które mogą dodawać funkcje na poziomie interfejsu
Martin
1
Myślę, że Złota Reguła ma „uczynić klasy nie-liścia abstrakcyjne”, ale nie stawia to żadnych wymagań „tylko czystych” lub „pustych”.
Kerrek SB,
1
Jeśli to działa dla ciebie, działa dla ciebie. Naprawdę nie rozumiem, dlaczego ludzie wpadają w panikę, gdy ich kod przestaje być zgodny z najnowszymi opiniami.
James

Odpowiedzi:

5

OOP ma skład i substytucję.

C ++ ma wiele elementów dziedziczenia, specjalizacji szablonów, osadzania i semantyki value / move / pointer.

Java ma pojedyncze dziedziczenie i interfejsy, osadzanie i semantykę odniesienia.

Powszechnym sposobem, w jaki szkoła OOP używa tych języków, jest stosowanie dziedziczenia do podstawiania obiektów i osadzania do kompozycji. Ale potrzebujesz także wspólnego przodka i sposobu na wykonanie w środowisku uruchomieniowym (w C ++ nazywa się dynamic_cast, w Javie po prostu pyta interfejsu od innego).

Java robi to wszystko przez swoją własną java.lang.Objectzakorzenioną hierarchię. C ++ nie ma predefiniowanego wspólnego katalogu głównego, więc powinieneś przynajmniej go zdefiniować, aby uzyskać ten sam „obraz” (ale to ogranicza niektóre możliwości C ++ ...).

Następnie możliwość posiadania polimorfizmu w czasie kompilacji (pomyśl o CRTP) i semantycznej wartości może zaoferować także inne alternatywy dla sposobu, w jaki koncepcja „obiektu OOP” może zostać przeniesiona do programu C ++.

Możesz nawet wyobrazić sobie, że herezja używa osadzania i niejawnej konwersji do zarządzania substytucją i prywatnym dziedziczeniem do zarządzania kompozycją, w rzeczywistości odwracając tradycyjny paradygmat szkolny. (Oczywiście w ten sposób jest o 20 lat młodszy od drugiego, więc nie oczekuj wsparcia ze strony szerokiej społeczności)

Lub możesz sobie wyobrazić wirtualną wspólną bazę dla wszystkich klas, od interfejsu interfejsu (bez implementacji) do klas końcowych (w pełni zaimplementowanych) przechodzących przez częściowo zaimplementowane interfejsy, a nawet klastry interfejsów, wykorzystując „dominację” jako wysyłanie z interfejsu do implementacji za pomocą „wielu stosów” -parallelogram ”schemat dziedziczenia.

Porównanie OOP do java z C ++ przy założeniu, że istnieje tylko jeden sposób OOP, ogranicza możliwości obu języków.

Zmuszanie C ++ do ścisłego przestrzegania idiomów kodujących Java oznacza denaturację C ++, ponieważ zmuszanie Javy do zachowywania się jak język C ++ denaturuje Javę.

Nie chodzi o „wrażliwość”, ale o różne „mechanizmy agregacji” dwóch języków i inny sposób ich łączenia, co sprawia, że ​​niektóre idiomy są bardziej opłacalne w jednym języku niż w drugim i odwrotnie.

Emilio Garavaglia
źródło
1
Myślę, że ta odpowiedź jest bardzo interesująca, ponieważ z powodzeniem opisuje funkcje językowe jako narzędzie dla oo, a zasady projektowania jedynie jako pomoc, a nie doktrynę. Jednak nie potrzebujesz wspólnego roota, jeśli chcesz robić oo w c ++. Jest to po prostu błędne, również dlatego, że masz operatory i szablony (które są bardzo potężną alternatywą dla głównego projektu drzewa Java, jak również wskazałeś). Poza tym twoje punkty są najbardziej wartościowe we wszystkich odpowiedziach
Martin
1
@Martin: „W sensie technicznym” masz rację, ale jeśli potrzebujesz runtime polymorophism (ponieważ rzeczywisty typ instancji obiektów zależy na wejściu programu) jest „root” ( „a” jest artykuł, a nie skrót dla " jeden i jedyny „) sprawia, że ​​wszystkie obiekty są„ kuzynami ”, a hierarchia jest dostępna do przejścia. Różne korzenie pochodzą z różnych przodków, które nie są ze sobą powiązane. To, czy jest to „dobre” czy „złe”, zależy od kontekstu, a nie idiomu.
Emilio Garavaglia,
To prawda. Myślałem, że masz na myśli sztucznie porowaty jeden główny katalog główny dla całego programu c ++ i zobaczyłem jako wadę, że nie jest obecny, w porównaniu z Javą. Ale po dokonaniu edycji uwaga jest całkiem jasna. Jeszcze raz dziękuję
Martin
12

Zasada obowiązuje dla obu języków, ale nie robisz uczciwego porównania. Powinieneś porównać klasy czysto abstrakcyjne C ++ z interfejsami Java.

Nawet w C ++ możesz mieć klasy abstrakcyjne, które mają zaimplementowane niektóre funkcje, ale wywodzą się z czystej klasy abstrakcyjnej (bez implementacji). W Javie masz te same abstrakcyjne klasy (z niektórymi implementacjami), które mogą pochodzić z interfejsów (bez implementacji).

Luchian Grigore
źródło
Kiedy więc wolisz klasę abstrakcyjną niż klasę interfejsu w c ++. Zawsze wybrałem interfejs plus funkcje nie będące członkami w c ++.
Martin
1
@ Martin, który zależy od projektu. Zasadniczo zawsze preferuj interfejs. Ale reguły „ zawsze ” mają wyjątki ...
Luchian Grigore,
To prawda, ale w kodzie Java widzę klasy abstrakcyjne, w większości reprezentujące większość. Czy może to wynikać z faktu, że darmowe funkcje działające na interfejsach nie są możliwe w Javie?
Martin
3
@Martin dobrze wolne funkcje nie są w ogóle możliwe w Javie, więc może to być powód, tak. Dobre miejsce! Odpowiedziałeś na swoje pytanie! Możesz sam dodać odpowiedź, myślę, że to wszystko.
Luchian Grigore
4

Zasadniczo te same zasady OO obowiązują w Javie i C ++. Jednak jedną wielką różnicą jest to, że C ++ obsługuje wielokrotne dziedziczenie, podczas gdy w Javie można dziedziczyć tylko z jednej klasy. To jest główny powód, dla którego Java ma interfejsy, które moim zdaniem uzupełniają brak wielokrotnego dziedziczenia i prawdopodobnie ograniczają to, co można z tym zrobić (ponieważ istnieje duża krytyka nadużywania wielokrotnego dziedziczenia). Zatem prawdopodobnie w umyśle programistów Java istnieje wyraźniejsze rozróżnienie między klasami abstrakcyjnymi a interfejsami. Klasy abstrakcyjne służą do udostępniania i dziedziczenia zachowania, a interfejsy są po prostu używane do dodawania dodatkowej funkcjonalności. Pamiętaj, że w Javie możesz dziedziczyć tylko jedną klasę, ale możesz mieć wiele interfejsów. Jednak w C ++ czysto abstrakcyjne klasy (tj. „Interfejs C ++”) to używane do dzielenia się i dziedziczenia zachowania w przeciwieństwie do celu interfejsu Java (chociaż nadal musisz implementować funkcje), dlatego użycie różni się od interfejsów Java.

Jesse Good
źródło
0

Czasami sensowne jest posiadanie domyślnej implementacji. Na przykład ogólna metoda PrintError (string msg), która ma zastosowanie do wszystkich podklas.

virtual PrintError(string msg) { cout << msg; }

W razie potrzeby można go w dalszym ciągu zastąpić, ale można zaoszczędzić klientowi kłopotów, pozwalając mu po prostu wywołać wersję ogólną.

Science_Fiction
źródło