Unikanie pułapek obiektowych, migracja z C, co zadziałało dla Ciebie?

12

Od dłuższego czasu programuję w językach proceduralnych, a moją pierwszą reakcją na problem jest rozpoczęcie dzielenia go na zadania do wykonania zamiast rozważania różnych istniejących bytów (obiektów) i ich relacji.

Mam kurs uniwersytecki w zakresie OOP i rozumiem podstawy enkapsulacji, abstrakcji danych, polimorfizmu, modułowości i dziedziczenia.

Przeczytałem /programming/2688910/learning-to-think-in-the-object-oriented-way i /programming/1157847/learning-object-oriented-thinking , i przyjrzymy się niektórym książkom wskazanym w tych odpowiedziach.

Myślę, że kilka moich średnich i dużych projektów skorzysta na efektywnym wykorzystaniu OOP, ale jako nowicjusz chciałbym uniknąć czasochłonnych, powszechnych błędów.

W oparciu o twoje doświadczenia, jakie są te pułapki i jakie są rozsądne sposoby ich rozwiązania? Jeśli potrafisz wyjaśnić, dlaczego są to pułapki i jak twoja sugestia skutecznie rozwiązuje ten problem, będzie to mile widziane.

Zastanawiam się nad czymś w rodzaju: „Czy często stosuje się sporo metod obserwatora i modyfikatora i używa zmiennych prywatnych, czy też istnieją techniki ich konsolidacji / redukcji?”

Nie martwię się o używanie C ++ jako czystego języka OO, jeśli istnieją dobre powody, aby mieszać metody. (Przypomina powody używania GOTO, aczkolwiek oszczędnie.)

Dziękuję Ci!

Stephen
źródło
2
nie warta pełnej odpowiedzi, ale ważna, która zajęła mi dużo czasu, aby ją zaakceptować (tj. czytałem o tym dużo czasu, ale traktowałem ją jako rozmowę z fanem): wolę bezpłatne funkcje od funkcji członkowskich. Dzięki temu twoje zajęcia są minimalne, co jest dobrą rzeczą.
stijn
@stijn Zasadniczo mówisz, że jeśli nie musi być w klasie, nie umieszczaj go tam. Na przykład widzę wiele funkcji członka narzędzia, które mogą być z łatwością funkcjami w kodzie, który czytałem do tej pory.
Stephen
tak, to jest to. Jeśli szukasz wyrażenia „wolisz nieprzyjaznego członka”, znajdziesz na ten temat wiele informacji. Ostatecznie sprowadza się to do przestrzegania zasady pojedynczej odpowiedzialności
stijn

Odpowiedzi:

10

Jedną wielką rzeczą, której się nauczyłem, jest projektowanie klas z zewnątrz. Zaprojektuj interfejs, zanim zaczniesz myśleć o implementacji. To sprawi, że klasa będzie o wiele bardziej intuicyjna dla użytkowników (osób korzystających z klasy) niż pisanie podstawowych algorytmów i budowanie klasy oraz pisanie nowych funkcji członka publicznego, jeśli będą potrzebne.

Dominic Gurto
źródło
7
Dodając do tego, możemy „programować z zamiarem”, to znaczy napisać przykładowy kod, który wykorzystałby nową klasę, zobaczyć, jakie rodzaje metod i zasad sprawiają, że korzystanie z niego jest wygodne. Następnie wykorzystaj te informacje jako podstawę do implementacji.
2
Świetny w teorii - projektowanie interfejsów i gotowe. Ale w praktyce nie działa to tak prosto. Projektowanie i implementacja interfejsu często idą w parze w kolejnych iteracjach, aż do ostatecznego skrystalizowania interfejsu. W tym czasie prawdopodobnie będziesz mieć także ostateczną implementację.
Gene Bushuyev
9

Po pierwsze, pułapką jest ujawnianie zbyt dużej ilości informacji. Domyślnie powinno być private, nie public.

Potem przychodzi zbyt wielu pobierających / ustawiających. Powiedzmy, że mam członka danych. Czy naprawdę potrzebuję tych danych w innej klasie? Ok, zrób gettera. Czy naprawdę muszę naprawdę zmieniać te dane w trakcie użytkowania obiektu? Następnie zrób setera.

Większość początkujących programistów ma domyślne ustawienie getter / setter dla każdego elementu danych. To zakłóca interfejsy i często jest złym wyborem projektu.

orlp
źródło
2
tak, jest to dość popularne wśród tych, którzy powierzchownie rozumieli enkapsulację, aby uczynić dane prywatnymi, a następnie wysyłać enkapsulację za pomocą metod pobierających i ustawiających.
Gene Bushuyev
Java to jedyny popularny język, który wciąż wymaga metod get / set. Każdy inny język obsługuje właściwości, więc nie ma różnicy składniowej między publicznym elementem danych a właściwością. Nawet w Javie nie jest grzechem mieć publicznych członków danych, jeśli kontrolujesz również wszystkich użytkowników klasy.
kevin cline
Członkowie danych publicznych są trudniejsi w utrzymaniu. Dzięki getterom / ustawiaczom (lub właściwościom) możesz zachować interfejs podczas zmiany wewnętrznej reprezentacji danych, bez zmiany kodu zewnętrznego. W językach, w których właściwości są składniowo identyczne ze zmiennymi składowymi z punktu widzenia konsumenta, ten punkt nie ma zastosowania.
tdammers
Do tej pory myślę, że patrzę na prywatnych członków „w pewnym sensie” jako zmienne lokalne w funkcji ... reszta świata nie musi nic o nich wiedzieć, aby fn działał ... a jeśli fn zmiany, tylko interfejs musi być spójny. Nie mam tendencji do używania metody pobierania / ustawiania spamu, więc mogę być na dobrej drodze, w rzeczywistości patrząc na klasę bufora, którą napisałem, w ogóle nie ma publicznych członków danych. Dzięki!
Stephen
2

Kiedy przekroczyłem tę przepaść, zdecydowałem się na następujące podejście:

0) Zacząłem powoli, z małymi / średnimi aplikacjami proceduralnymi i bez pracy o znaczeniu krytycznym.

1) prostym mapowaniem pierwszego przejścia z tego, jak napisałbym program od zera w stylu OO - co najważniejsze dla mnie w tym czasie, a to JEST subiektywne - miało na celu wykrycie wszystkich klas podstawowych. Moim celem było kapsułkowanie jak największej liczby klas podstawowych. Czyste metody wirtualne dla wszystkiego, co możliwe w klasach podstawowych.

2) Następnie następnym krokiem było utworzenie pochodnych.

3) ostatnim krokiem było - w oryginalnym kodzie proceduralnym oddzielić struktury danych od kodu obserwatora / modyfikatora. Następnie użyj ukrywania danych i zamapuj wszystkie wspólne dane na klasy podstawowe, aw podklasach poszły dane, które nie były wspólne w programie. I to samo traktowanie kodu obserwatora / modyfikatora proceduralnego - cała logika „wszędzie używana” trafiła do klas podstawowych. I zobacz / zmodyfikuj logikę, która działała tylko na podzbiorze danych, trafiła do klas pochodnych.

Jest to subiektywne, ale jest SZYBKIE, jeśli dobrze znasz kod proceduralny i struktury danych. Błąd w przeglądzie kodu występuje wtedy, gdy odrobina danych lub logiki nie pojawia się w klasach podstawowych, ale jest używana wszędzie.


źródło
2

Spójrz na inne udane projekty, które wykorzystują OOP, aby uzyskać poczucie dobrego stylu. Polecam przyjrzeć się projektowi Qt , do którego zawsze podchodzę, podejmując własne decyzje projektowe.

rcv
źródło
Tak! Zanim ktoś stanie się mistrzem czegoś, uczy się naśladować wielkich mistrzów, którzy przed nim pracowali. Studiowanie dobrego kodu wdrożonego przez innych jest dobrym sposobem na naukę. Poleciłbym przyjrzeć się wzmocnieniu, zaczynając od prostych projektów i sięgając głębiej, gdy poprawia się zrozumienie.
Gene Bushuyev
1

W zależności od tego, z kim rozmawiasz, wszystkie dane w OOP powinny być prywatne lub chronione i dostępne tylko za pośrednictwem akcesoriów i mutatorów. Ogólnie uważam, że jest to dobra praktyka, ale zdarzają się sytuacje, w których odstępuję od tej normy. Na przykład, jeśli masz klasę (powiedzmy w Javie), której jedynym celem jest powiązanie niektórych danych w jednostkę logiczną, warto pozostawić pola publiczne. Jeśli jest to niezmienna kapsuła, po prostu zaznacz je jako ostateczne i zainicjuj je w konstruktorze. Sprowadza to klasę (w tym przypadku) do niewiele więcej niż struktury (w rzeczywistości przy użyciu C ++ powinieneś nazwać ją strukturą. Działa tak jak klasa, ale domyślna widoczność jest publiczna, a twoja intencja jest wyraźniejsza), ale Myślę, że przekonasz się, że korzystanie z niego jest w tych przypadkach znacznie wygodniejsze.

Jedną rzeczą, której zdecydowanie nie chcesz robić, jest posiadanie pola, które musi być sprawdzone pod kątem spójności w mutatorze i pozostawienie go publicznego.

jpm
źródło
3
Sugerowałbym również, jeśli masz takie klasy, które przechowują tylko dane publiczne, powinieneś zadeklarować je jako structs - nie ma to znaczenia semantycznego, ale wyjaśnia intencję i sprawia, że ​​ich użycie wydaje się bardziej naturalne, szczególnie dla programistów C.
1
Tak, zgadzam się tutaj, jeśli jesteś w C ++ (o którym wspomniałem w pytaniu), ale ja większość pracy wykonuję w Javie i nie mamy struktury, niestety.
musisz wyraźnie rozróżnić klasy agregujące (rzadkie przypadki) i klasy z funkcjonalnością (ogólne). Ten ostatni musi ćwiczyć enkapsulację i uzyskiwać dostęp do swoich członków tylko za pośrednictwem publicznego interfejsu, żadne dane nie powinny być ujawniane publicznie.
Gene Bushuyev
1

Książka Kenta Becka Implementation Patterns stanowi doskonałą podstawę do używania, a nie niewłaściwego używania mechanizmów obiektowych.

Steven A. Lowe
źródło
1

Nie martwię się o używanie C ++ jako czystego języka OO, jeśli istnieją dobre powody, aby mieszać metody. (Przypomina powody używania GOTO, aczkolwiek oszczędnie.)

Tak naprawdę nie sądziłem, że mam dużo do zaoferowania, dopóki nie zobaczyłem tego fragmentu. Muszę się nie zgodzić z tym sentymentem. OOP jest tylko jednym z paradygmatów, które mogą i powinny być używane w C ++. Szczerze mówiąc, moim zdaniem nie jest to jedna z jego najsilniejszych cech.

Z punktu widzenia OO wydaje mi się, że C ++ jest trochę za krótki. Na przykład pomysł posiadania funkcji nie-wirtualnych jest pod tym względem kleszczem. Kłóciłem się z tymi, którzy się ze mną nie zgadzają, ale nie-wirtualni członkowie po prostu nie pasują do tego paradygmatu. Polimorfizm jest kluczowym składnikiem OO, a klasy z funkcjami innymi niż wirtualne nie są polimorficzne w sensie OO. Więc jako język OO uważam, że C ++ jest raczej słaby w porównaniu do języków takich jak Java czy Objective-C.

Z drugiej strony programowanie ogólne, C ++ ma to całkiem dobre. Słyszałem, że są też lepsze języki do tego, ale połączenie obiektów i funkcji ogólnych jest dość potężne i wyraziste. Co więcej, może być cholernie szybki zarówno w czasie programowania ORAZ czasu przetwarzania. Wydaje mi się, że to właśnie w tym obszarze C ++ świeci, choć trzeba przyznać, że mogłoby być lepiej (na przykład obsługa języka dla koncepcji). Ktoś, kto myśli, że powinien trzymać się paradygmatu OO i traktować innych zgodnie z instrukcją goto na poziomach niemoralności, tak naprawdę nie dostrzega tego paradygmatu.

Imponująca jest także zdolność metaprogramowania szablonów. Sprawdź na przykład bibliotekę Boost.Units. Ta biblioteka zapewnia obsługę typów dla wielkości wymiarowych. Szeroko wykorzystałem tę bibliotekę w firmie inżynierskiej, w której obecnie pracuję. Zapewnia tylko o wiele bardziej natychmiastową informację zwrotną dla jednego aspektu możliwego programisty, a nawet błędu specyfikacji. Niemożliwe jest skompilowanie programu, który używa formuły, w której obie strony operatora „=” nie są równoważne wymiarowo bez jawnego rzutowania. Osobiście nie mam doświadczenia z żadnym innym językiem, w którym jest to możliwe, a na pewno nie z takim, który ma moc i szybkość C ++.

Metaprogramowanie jest paradygmatem czysto funkcjonalnym.

Tak naprawdę myślę, że już wkraczasz do C ++ z kilkoma niefortunnymi błędami. Innych paradygmatów oprócz OO nie należy unikać, należy je ZWOLNIĆ. Użyj paradygmatu, który jest naturalny dla aspektu problemu, nad którym pracujesz. Nie zmuszaj obiektów do tego, co zasadniczo nie jest problemem podatnym na obiekty. Jeśli o mnie chodzi, OO nie jest nawet połową historii C ++.

Edward Strange
źródło
Czy wirtualne słowo kluczowe nie zapewnia funkcji klasy wirtualnej w C ++? Jeśli chodzi o komentarz goto, kierowałem się tym, że nie martwię się łamaniem zasad empirycznych, o ile rozumiem rozumowanie. Ja jednak patrzyłem dalej w kierunku unikania nakazów / procedur na rzecz OO, niż mogłoby to być konieczne. Dzięki.
Stephen
metody wirtualne nie są warunkiem polimorfizmu, to po prostu powszechny sposób. w rzeczywistości wszystko inne jest równe, a następnie uczynienie metody wirtualnej faktycznie osłabia enkapsulację, ponieważ zwiększasz rozmiar api klasy i utrudniasz śledzenie Liskova i tak dalej. zrób coś wirtualnego tylko wtedy, gdy potrzebujesz szwu, jakiegoś miejsca, w którym można wprowadzić nowe zachowanie poprzez dziedziczenie (chociaż dziedziczenie jest również ostrożne w OOP). virtual dla samego wirtualnego nie czyni klasy „więcej OOP”
sara
1

Chciałem zaakceptować odpowiedź na to pytanie, ale nie mogłem zdecydować się na jedną odpowiedź, aby nadać znacznik wyboru. W związku z tym głosowałem za oryginalnymi autorami i stworzyłem to jako odpowiedź podsumowującą. Dzięki wszystkim, którzy poświęcili kilka minut, odkryłem, że wgląd, który dostarczyłeś, dał mi dobry kierunek i zapewnił mnie, że nie jestem poza torami.

@nightcracker

Po pierwsze, pułapką jest ujawnianie zbyt dużej ilości informacji. Wartość domyślna powinna być prywatna, a nie publiczna. Potem przychodzi zbyt wielu pobierających / ustawiających.

Czułem, że w przeszłości obserwowałem ten problem w działaniu. Wasze komentarze przypomniały mi również, że ukrywając zmienne leżące u ich podstaw i ich implementację, mogę zmienić ich implementację, nie niszcząc niczego od nich zależnego.

Dominic Gurto

Zaprojektuj interfejs, zanim zaczniesz myśleć o implementacji. Gene Bushuyev Projektowanie i implementacja interfejsu często idą w parze w kolejnych iteracjach, aż do ostatecznego skrystalizowania interfejsu.

Myślałem, że komentarz Dominika był świetnym ideałem, do którego można dążyć, ale myślę, że komentarz Gene'a naprawdę pasuje do rzeczywistości. Do tej pory widziałem to w akcji ... i czuję się trochę lepiej, że nie jest to rzadkie. Myślę, że gdy dojrzewam jako programista, skłaniam się ku bardziej kompletnym projektom, ale teraz wciąż cierpię z powodu skoku i otrzymywania kodu.

wantTheBest

Zacząłem powoli, z małymi / średnimi aplikacjami proceduralnymi i bez pracy o znaczeniu krytycznym. w oryginalnym kodzie proceduralnym oddziel struktury danych od kodu obserwatora / modyfikatora

To ma sens ... Podobał mi się pomysł utrzymywania pracy w pracy, ale refaktoryzacja niektórych niekrytycznych rzeczy za pomocą klas.

jpm

Jedną rzeczą, której zdecydowanie nie chcesz robić, jest posiadanie pola, które musi być sprawdzone pod kątem spójności w mutatorze i pozostawienie go publicznego

Od dłuższego czasu wiedziałem, że jest to jedna z zalet enkapsulacji danych ... możliwość wymuszenia spójności, a co za tym idzie, warunków / zakresów itp.

Szalony Eddie

Ktoś, kto myśli, że powinien trzymać się paradygmatu OO i traktować innych zgodnie z instrukcją goto na poziomach niemoralności, naprawdę nie dostrzega tego, nie patrząc na ten paradygmat. Imponująca jest także zdolność metaprogramowania szablonów.

Myślę, że początkowo bardzo brakowało mi odpowiedzi Szalonego Eddiego, ponieważ nie przeczytałem niektórych z wymienionych tematów ... takich jak metaprogramowanie. Myślę, że świetnym przesłaniem w poście CE było to, że C ++ jest tak mieszanką możliwości i stylów, że każdy z nich powinien być wykorzystany do ich najlepszego potencjału ... łącznie z koniecznością, jeśli to ma sens.

Jeszcze raz dziękuję wszystkim, którzy odpowiedzieli!

Stephen
źródło
0

Największą pułapką jest przekonanie, że OOP to srebrna kula lub „jeden idealny paradygmat”.

alternatywny
źródło