Przestudiowałem książkę „C ++ Demystified” . Teraz zacząłem czytać „Object-Oriented Programming in Turbo C ++ pierwsze wydanie (1. wydanie)” Roberta Lafore'a. Nie mam żadnej wiedzy o programowaniu, która wykracza poza te książki. Ta książka może być nieaktualna, ponieważ ma 20 lat. Mam najnowsze wydanie, używam starego, ponieważ mi się podoba, głównie studiuję podstawowe koncepcje OOP używane w C ++ w pierwszym wydaniu książki Lafore.
Książka Lafore podkreśla, że „OOP” jest użyteczny tylko w przypadku większych i złożonych programów. W każdej książce OOP (także w książce Lafore'a) jest powiedziane, że paradygmat proceduralny jest podatny na błędy, np. Dane globalne, które są podatne na działanie funkcji. Mówi się, że programista może popełniać uczciwe błędy w językach proceduralnych, np. Wykonując funkcję, która przypadkowo uszkadza dane.
Szczerze mówiąc, zamieszczam moje pytanie, ponieważ nie rozumiem wyjaśnień zawartych w tej książce: Programowanie obiektowe w C ++ (wydanie 4). Nie rozumiem tych stwierdzeń napisanych w książce Lafore:
Programowanie obiektowe zostało opracowane, ponieważ we wcześniejszych podejściach do programowania odkryto ograniczenia .... W miarę jak programy stają się coraz większe i bardziej złożone, nawet podejście do programowania strukturalnego zaczyna wykazywać oznaki obciążenia ... .... Analizując przyczyny niepowodzenia te ujawniają słabości samego paradygmatu proceduralnego. Bez względu na to, jak dobrze wdrażane jest podejście programowania strukturalnego, duże programy stają się nadmiernie złożone ... ... Istnieją dwa powiązane problemy. Po pierwsze, funkcje mają nieograniczony dostęp do danych globalnych. Po drugie, niepowiązane funkcje i dane, będące podstawą paradygmatu proceduralnego, stanowią zły model realnego świata ...
Studiowałem książkę „dysmystified C ++” Jeffa Kenta, bardzo ją lubię, w tej książce wyjaśniono głównie programowanie proceduralne. Nie rozumiem, dlaczego programowanie proceduralne (strukturalne) jest słabe!
Książka Lafore'a bardzo ładnie wyjaśnia tę koncepcję, podając kilka dobrych przykładów. Zrozumiałem też intuicję, czytając książkę Lafore'a, że OOP jest lepsze niż programowanie proceduralne, ale jestem ciekawy, jak dokładnie w praktyce programowanie proceduralne jest słabsze niż OOP.
Chcę się przekonać, jakie są praktyczne problemy, z jakimi można się spotkać w programowaniu proceduralnym, w jaki sposób OOP ułatwi programowanie. Myślę, że otrzymam odpowiedź, czytając po prostu książkę Lafore'a, ale chcę zobaczyć na własne oczy problemy z kodem proceduralnym, chcę zobaczyć, w jaki sposób kod w stylu OOP programu usuwa powyższe błędy, które wystąpiłyby, gdyby ten sam program miał być napisany z wykorzystaniem paradygmatu proceduralnego.
Istnieje wiele funkcji OOP i rozumiem, że ktoś nie może mi wyjaśnić, w jaki sposób wszystkie te funkcje usuwają powyższe błędy, które generowałyby się, pisząc kod w stylu proceduralnym.
Oto moje pytanie:
Jakie ograniczenia programowania proceduralnego rozwiązuje OOP i jak skutecznie usuwa te ograniczenia w praktyce?
W szczególności, czy istnieją przykłady programów trudnych do zaprojektowania za pomocą paradygmatu proceduralnego, ale które można łatwo zaprojektować za pomocą OOP?
PS: Krzyż wysłany z: /programming//q/22510004/3429430
źródło
Odpowiedzi:
W języku proceduralnym niekoniecznie wyrażasz ograniczenia, które są wymagane, aby udowodnić, że osoba dzwoniąca używa modułu w obsługiwany sposób. W przypadku braku ograniczenia możliwego do sprawdzenia przez kompilator, musisz napisać dokumentację i mieć nadzieję, że będzie ona przestrzegana, oraz użyj testów jednostkowych, aby wykazać zamierzone zastosowania.
Deklarowanie typów jest najbardziej oczywistym ograniczeniem deklaratywnym (tj .: „udowodnij, że x jest liczbą zmiennoprzecinkową”). Zmuszanie mutacji danych do przejścia przez funkcję, o której wiadomo, że jest zaprojektowane dla tych danych, to kolejna sprawa. Wymuszanie protokołu (kolejność wywoływania metod) to kolejne częściowo obsługiwane ograniczenie, tj .: „Konstruktor -> Inne metody * -> Destruktor”.
Istnieją również prawdziwe zalety (i kilka wad), gdy kompilator wie o wzorcu. Wpisywanie statyczne z typami polimorficznymi stanowi pewien problem, gdy emulujesz enkapsulację danych z języka proceduralnego. Na przykład:
typ x1 jest podtypem x, t1 jest podtypem t
Jest to jeden ze sposobów enkapsulacji danych w języku proceduralnym w celu uzyskania typu t metodami f i g oraz podklasy t1, która działa podobnie:
t_f (t, x, y, z, ...), t_g (t, x, y, ...) t1_f (t1, x, y, z, ...)
Aby użyć tego kodu w obecnej postaci, musisz sprawdzić typ i włączyć typ t przed podjęciem decyzji o rodzaju f, który chcesz wywołać. Możesz obejść to w ten sposób:
wpisz t {d: dane f: funkcja g: funkcja}
Aby zamiast tego wywołać tf (x, y, z), w którym sprawdzenie typu i przełącznik w celu znalezienia metody jest teraz zastępowany jedynie jawnym wskazywaniem wskaźników instancji dla każdej instancji. Teraz, jeśli masz ogromną liczbę funkcji dla każdego typu, jest to marnotrawna reprezentacja. Mógłbyś wtedy użyć innej strategii, takiej jak wskazanie t zmiennej m, która zawiera wszystkie funkcje składowe. Jeśli ta funkcja jest częścią języka, możesz pozwolić kompilatorowi dowiedzieć się, jak radzić sobie z wydajnym przedstawieniem tego wzorca.
Ale enkapsulacja danych to rozpoznanie, że stan zmiennych jest zły. Obiektowym rozwiązaniem jest ukrywanie go za metodami. Idealnie byłoby, gdyby wszystkie metody w obiekcie miały dobrze zdefiniowaną kolejność wywoływania (tj .: konstruktor -> open -> [read | write] -> close -> destruct); który jest czasem nazywany „protokołem” (badanie: „Microsoft Singularity”). Ale poza konstruowaniem i niszczeniem, wymagania te na ogół nie są częścią systemu typów - ani dobrze udokumentowane. W tym sensie obiekty są równoczesnymi wystąpieniami maszyn stanów, które są przenoszone przez wywołania metod; tak, że możesz mieć wiele instancji i używać ich w sposób arbitralnie przeplatany.
Ale rozpoznając, że zmienny stan współdzielenia jest zły, można zauważyć, że orientacja obiektowa może stworzyć problem współbieżności, ponieważ struktura danych obiektowych jest stanem zmiennym, do którego odwołuje się wiele obiektów. Większość języków zorientowanych obiektowo wykonuje się w wątku wywołującego, co oznacza, że w wywołaniu metody występują warunki wyścigu; nie mówiąc już o nieatomowych ciągach wywołań funkcji. Alternatywnie, każdy obiekt może otrzymywać wiadomości asynchroniczne w kolejce i obsługiwać je wszystkie w wątku obiektu (metodami prywatnymi) i odpowiadać na telefon wywołujący, wysyłając mu wiadomości.
Porównaj wywołania metod Java w kontekście wielowątkowym z procesami Erlanga wysyłającymi do siebie wiadomości (które odwołują się tylko do niezmiennych wartości).
Nieograniczona orientacja obiektu w połączeniu z równoległością stanowi problem z powodu blokowania. Istnieją techniki od Software Transactional Memory (tj. Transakcje ACID w obiekcie pamięci podobnym do baz danych) po stosowanie podejścia „współdziel pamięć przez komunikację (dane niezmienne)” (hybrydowe programowanie funkcjonalne).
Moim zdaniem literatura Object Orientation zbytnio koncentruje FAR na dziedziczeniu, a zbyt mało na protokole (porządkowanie wywoływanych metod, warunki wstępne, warunki dodatkowe itp.). Dane wejściowe, które zużywa obiekt, powinny zwykle mieć dobrze zdefiniowaną gramatykę, wyrażalną jako typ.
źródło
Programowanie proceduralne / funkcjonalne nie jest w żaden sposób słabsze niż OOP , nawet bez wchodzenia w argumenty Turinga (mój język ma moc Turinga i może robić cokolwiek innego, co robi), co niewiele znaczy. W rzeczywistości techniki obiektowe po raz pierwszy eksperymentowano w językach, które nie miały ich wbudowanych. W tym sensie programowanie OO jest tylko specyficznym stylem programowania proceduralnego . Ale to pomaga egzekwowania konkretnych dyscyplin, takich jak modułowość, abstrakcji i ukrywania informacji , które są niezbędne dla zrozumiałość i konserwacji programu.
Niektóre paradygmaty programowania ewoluują od teoretycznej wizji obliczeń. Język taki jak Lisp ewoluował z rachunku lambda i idei meta-okrężności języków (podobnej do zwrotności w języku naturalnym). Klauzule rogowe były prologiem i programowaniem ograniczeń. Rodzina Algol zawdzięcza także rachunku lambda, ale bez wbudowanej refleksyjności.
Lisp jest interesującym przykładem, ponieważ był testem wielu innowacji w języku programowania, które można powiązać z jego podwójnym dziedzictwem genetycznym.
Jednak języki ewoluują, często pod nowymi nazwami. Głównym czynnikiem ewolucji jest praktyka programowania. Użytkownicy identyfikują praktyki programistyczne, które poprawiają właściwości programów, takie jak czytelność, łatwość konserwacji, sprawdzalność poprawności. Następnie próbują dodać do języków funkcje lub ograniczenia, które będą obsługiwać, a czasem egzekwować te praktyki, aby poprawić jakość programów.
Oznacza to, że praktyki te są już możliwe w starszym języku programowania, ale ich zrozumienie i dyscyplina wymagają ich zastosowania. Włączenie ich do nowych języków jako podstawowych pojęć o określonej składni sprawia, że praktyki te są łatwiejsze w użyciu i łatwiejsze do zrozumienia, szczególnie dla mniej zaawansowanych użytkowników (tj. Zdecydowanej większości). Ułatwia także życie zaawansowanym użytkownikom.
W pewien sposób chodzi o zaprojektowanie języka, czym jest podprogram / funkcja / procedura dla programu. Po zidentyfikowaniu użytecznego pojęcia otrzymuje się nazwę (ewentualnie) i składnię, dzięki czemu można go łatwo używać we wszystkich programach opracowanych w tym języku. A gdy odniesie sukces, zostanie również włączony do przyszłych języków.
Przykład: odtworzenie orientacji obiektu
Teraz staram się to zilustrować na przykładzie (który z pewnością można by dopracować, biorąc pod uwagę czas). Celem tego przykładu nie jest pokazanie, że program zorientowany obiektowo może być napisany w proceduralnym stylu programowania, być może kosztem czytelności i łatwości obsługi. Spróbuję raczej pokazać, że niektóre języki bez funkcji OO mogą faktycznie korzystać z funkcji wyższego rzędu i struktury danych, aby faktycznie tworzyć środki do skutecznego naśladowania orientacji obiektowej , aby skorzystać z jej zalet w zakresie organizacji programu, w tym modułowości, abstrakcji i ukrywania informacji .
Jak powiedziałem, Lisp był testem wielu ewolucji języka, w tym paradygmatu OO (chociaż tym, co można uznać za pierwszy język OO, była Simula 67, w rodzinie Algol). Lisp jest bardzo prosty, a kod jego podstawowego interpretera jest mniejszy niż strona. Ale możesz programować OO w Lisp. Wszystko czego potrzebujesz to funkcje wyższego rzędu.
Nie będę używać ezoterycznej składni Lisp, ale raczej pseudo-kod, aby uprościć prezentację. Rozważę prosty podstawowy problem: ukrywanie informacji i modułowość . Definiowanie klasy obiektów, jednocześnie uniemożliwiając użytkownikowi dostęp do (większości) implementacji.
Załóżmy, że chcę utworzyć klasę o nazwie wektor, reprezentującą wektory dwuwymiarowe, z metodami obejmującymi: dodawanie wektora, rozmiar wektora i równoległość.
Następnie mogę przypisać utworzony wektor do rzeczywistych nazw funkcji, które będą używane.
[wektor, xcoord, ycoord, vplus, vsize, vparallel] = vectorclass ()
Po co być tak skomplikowanym? Ponieważ mogę zdefiniować w funkcji vectorrec konstrukcje pośredniczące, że nie chcę być widoczny dla reszty programu, aby zachować modułowość.
Możemy wykonać kolejną kolekcję we współrzędnych biegunowych
Ale mogę używać obojętnie obu implementacji. Jednym ze sposobów jest dodanie komponentu typu do wszystkich wartości i zdefiniowanie wszystkich powyższych funkcji w tym samym środowisku: Następnie mogę zdefiniować każdą z zwracanych funkcji, aby najpierw przetestowała typ współrzędnych, a następnie zastosowała określoną funkcję dla tego.
Co zyskałem: określone funkcje pozostają niewidoczne (ze względu na zakres identyfikatorów lokalnych), a reszta programu może korzystać tylko z najbardziej abstrakcyjnych funkcji zwróconych przez wywołanie klasy wektorowej.
Jednym zastrzeżeniem jest to, że mógłbym bezpośrednio zdefiniować każdą z funkcji abstrakcyjnych w programie i pozostawić definicję funkcji zależnych od typu współrzędnych. Wtedy też byłby ukryty. To prawda, ale wtedy kod dla każdego typu współrzędnych zostałby pocięty na małe części rozłożone w programie, co jest mniej edytowalne i łatwe do utrzymania.
W rzeczywistości nie muszę nawet nadawać im nazwy i mógłbym po prostu zachować anonimowe wartości funkcjonalne w strukturze danych indeksowane według typu i ciągu reprezentującego nazwę funkcji. Ta struktura lokalna dla wektora funkcji byłaby niewidoczna dla reszty programu.
Aby uprościć użycie, zamiast zwracać listę funkcji, mogę zwrócić pojedynczą funkcję o nazwie Apply, przyjmując jako argument jawnie wpisaną wartość i ciąg znaków oraz zastosować funkcję o odpowiednim typie i nazwie. Wygląda to bardzo podobnie do wywoływania metody dla klasy OO.
Zatrzymam się tutaj w tej rekonstrukcji obiektu zorientowanego obiektowo.
Starałem się pokazać, że zbudowanie użytecznej orientacji obiektowej w wystarczająco mocnym języku, w tym dziedziczenia i innych podobnych cech, nie jest trudne. Metakrążenie interpretera może pomóc, ale głównie na poziomie syntaktycznym, co wciąż jest dalekie od nieistotnego.
Pierwsi użytkownicy orientacji obiektowej eksperymentowali w ten sposób z koncepcjami. Dotyczy to ogólnie wielu ulepszeń języków programowania. Oczywiście, analiza teoretyczna również odgrywa rolę i pomogła zrozumieć lub udoskonalić te pojęcia.
Ale pomysł, że języki, które nie mają funkcji OO są skazane na niepowodzenie w niektórych projektach, jest po prostu nieuzasadniony. W razie potrzeby mogą dość skutecznie naśladować implementację tych funkcji. Wiele języków ma moc syntaktyczną i semantyczną, aby dość skutecznie wykonywać orientację obiektową, nawet jeśli nie jest wbudowana. A to więcej niż argument Turinga.
OOP nie uwzględnia ograniczeń innych języków, ale wspiera lub wymusza metody programowania, które pomagają pisać lepszy program, pomagając w ten sposób mniej doświadczonym użytkownikom w stosowaniu dobrych praktyk, których bardziej zaawansowani programiści używali i rozwijali bez tego wsparcia.
Uważam, że dobrą książką, która to wszystko zrozumie, może być Abelson i Sussman: struktura i interpretacja programów komputerowych .
źródło
Myślę, że trochę historii jest w porządku.
Era od połowy lat sześćdziesiątych do połowy lat siedemdziesiątych jest dziś znana jako „kryzys oprogramowania”. Nie potrafię tego lepiej ująć niż Dijkstra w wykładzie z nagrody Turinga z 1972 r .:
To był czas pierwszych 32-bitowych komputerów, pierwszych prawdziwych wieloprocesorów i pierwszych komputerów wbudowanych, i dla badaczy było jasne, że będą one ważne dla programowania w przyszłości. Był to czas w historii, w którym po raz pierwszy zapotrzebowanie klientów przekroczyło możliwości programistyczne.
Nic dziwnego, że był to niezwykle płodny okres w programowaniu badań. Przed połową lat sześćdziesiątych mieliśmy LISP i AP / L, ale „główne” języki były zasadniczo proceduralne: FORTRAN, ALGOL, COBOL, PL / I i tak dalej. Od połowy lat 60. do połowy lat 70. dostaliśmy Logo, Pascal, C, Forth, Smalltalk, Prolog, ML i Modula, a to nie liczy DSL, takich jak SQL i jego poprzednicy.
Był to także czas w historii, w którym opracowywano wiele kluczowych technik wdrażania języków programowania. W tym okresie otrzymaliśmy analizę LR, analizę przepływu danych, wspólną eliminację podwyrażeń i pierwsze rozpoznanie, że niektóre problemy kompilatora (np. Alokacja rejestru) były trudne dla NP, i próbowaliśmy sobie z nimi poradzić.
W tym kontekście powstał OOP. Tak więc odpowiedź na pytanie, jakie problemy OOP rozwiązuje w praktyce na początku lat siedemdziesiątych, pierwsza odpowiedź jest taka, że wydawało się, że rozwiązało wiele problemów (zarówno współczesnych, jak i przewidywanych), z którymi borykali się programiści w tym okresie historii. Nie jest to jednak czas, kiedy OO stało się głównym nurtem. Wkrótce do tego dojdziemy.
Kiedy Alan Kay wymyślił termin „obiektowy”, miał na myśli obraz, że systemy oprogramowania będą miały strukturę biologiczną. Miałbyś coś w rodzaju pojedynczych komórek („obiektów”), które współdziałają ze sobą, wysyłając coś analogicznego do sygnałów chemicznych („wiadomości”). Nie można (a przynajmniej nie) zajrzeć do wnętrza komórki; wchodzilibyście w interakcję tylko za pośrednictwem ścieżek sygnalizacyjnych. Co więcej, możesz mieć więcej niż jedną komórkę każdego rodzaju, jeśli zajdzie taka potrzeba.
Widać tutaj, że istnieje kilka ważnych tematów: koncepcja dobrze zdefiniowanego protokołu sygnalizacyjnego (we współczesnej terminologii, interfejs), koncepcja ukrywania implementacji z zewnątrz (we współczesnej terminologii, prywatności) oraz koncepcja mając wiele „rzeczy” tego samego typu kręcących się w tym samym czasie (w nowoczesnej terminologii, instancji).
Brakuje jednej rzeczy, którą możesz zauważyć, a mianowicie dziedziczenia, i jest ku temu powód.
Programowanie obiektowe jest pojęciem abstrakcyjnym, a pojęcie abstrakcyjne można wdrażać na różne sposoby w różnych językach programowania. Na przykład abstrakcyjna koncepcja „metody” mogłaby zostać zaimplementowana w C za pomocą wskaźników funkcji, aw C ++ za pomocą funkcji składowych oraz w Smalltalk za pomocą metod (co powinno być zaskakujące, ponieważ Smalltalk implementuje abstrakcyjną koncepcję niemal bezpośrednio). To właśnie ludzie mają na myśli, gdy zwracają uwagę (całkiem słusznie), że można „robić” OOP w (prawie) dowolnym języku.
Dziedziczenie jest natomiast konkretną cechą języka programowania. Dziedziczenie może być przydatne do wdrażania systemów OOP. A przynajmniej tak było do wczesnych lat dziewięćdziesiątych.
Czas od połowy lat 80. do połowy lat 90. był także czasem w historii, gdy wszystko się zmieniało. W tym czasie pojawił się tani, wszechobecny 32-bitowy komputer, więc firmy i wiele domów mogło sobie pozwolić na umieszczenie na każdym biurku komputera, który był prawie tak potężny jak mainframe najniższej klasy dnia. Był to także okres świetności. To był także okres rozwoju nowoczesnego GUI i sieciowego systemu operacyjnego.
Właśnie w tym kontekście pojawiła się analiza i projektowanie obiektowe.
Wpływ OOAD, pracy „trzech Amigos” (Booch, Rumbar i Jacobsona) i innych (np. Metoda Shlaera – Mellora, projektowanie zorientowane na odpowiedzialność itp.) Nie może być niedoceniany. To jest powód, dla którego większość nowych języków, które zostały opracowane od początku lat 90. (przynajmniej większość z tych, o których słyszałeś) ma obiekty w stylu Simula.
Tak więc odpowiedź na twoje pytanie z lat 90. brzmi: obsługuje najlepsze (w tym czasie) rozwiązanie dla analizy zorientowanej na domenę i metodologii projektowania.
Od tego czasu, ponieważ mieliśmy młotek, zastosowaliśmy OOP do prawie każdego problemu, który pojawił się od tego czasu. OOAD i użyty przez niego model obiektowy zachęcały do zwinnego i opartego na testach rozwoju, klastra i innych systemów rozproszonych itd.
Nowoczesne GUI i wszelkie systemy operacyjne, które zostały zaprojektowane w ciągu ostatnich 20 lat, zwykle świadczą swoje usługi jako obiekty, więc każdy nowy praktyczny język programowania potrzebuje przynajmniej sposobu na połączenie się z systemami, z których obecnie korzystamy.
Tak więc współczesna odpowiedź brzmi: rozwiązuje problem łączenia się ze współczesnym światem. Współczesny świat zbudowany jest na OOP z tego samego powodu, dla którego świat 1880 został zbudowany na parze: rozumiemy go, możemy go kontrolować i wystarczająco dobrze sobie radzi.
Nie oznacza to oczywiście, że badania kończą się tutaj, ale zdecydowanie wskazuje, że każda nowa technologia będzie wymagać OO jako ograniczającego przypadku. Nie musisz być OO, ale nie możesz być z nim zasadniczo niezgodny.
źródło
Naprawdę brak. OOP tak naprawdę nie rozwiązuje problemu, mówiąc ściśle; nic nie można zrobić z systemem obiektowym, czego nie można zrobić z systemem nie zorientowanym obiektowo - w rzeczywistości nic nie można zrobić z żadnym z nich, czego nie można zrobić za pomocą maszyny Turinga. W końcu wszystko zamienia się w kod maszynowy, a ASM z pewnością nie jest obiektowy.
To, co robi dla ciebie paradygmat OOP, ułatwia organizowanie zmiennych i funkcji oraz pozwala łatwiej je przenosić.
Powiedz, że chcę napisać grę karcianą w języku Python. Jak miałbym reprezentować karty?
Gdybym nie wiedział o OOP, mógłbym to zrobić w ten sposób:
Prawdopodobnie napisałbym kod, aby wygenerować te karty, zamiast pisać je ręcznie, ale masz rację. „1S” oznacza 1 pik, „JD” oznacza walet karo i tak dalej. Potrzebowałbym również kodu dla Jokera, ale udajemy, że na razie nie ma Jokera.
Teraz, gdy chcę przetasować talię, potrzebuję tylko „przetasować” listę. Następnie, aby zdjąć kartę ze szczytu talii, usuwam górny wpis z listy, podając mi sznurek. Prosty.
Teraz, jeśli chcę dowiedzieć się, z którą kartą pracuję w celu wyświetlenia jej w odtwarzaczu, potrzebowałbym takiej funkcji:
Trochę duży, długi i nieefektywny, ale działa (i jest bardzo mało mityczny, ale tutaj nie ma sensu).
Co teraz, jeśli chcę, aby karty mogły poruszać się po ekranie? Muszę jakoś zapamiętać ich pozycję. Mógłbym dodać to na końcu kodu karty, ale to może być trochę niewygodne. Zamiast tego stwórzmy inną listę, gdzie jest każda karta:
Następnie piszę mój kod, aby indeks pozycji każdej karty na liście był taki sam jak indeks samej karty w talii.
A przynajmniej powinno być. Chyba że popełniam błąd. Co mogę bardzo dobrze, ponieważ mój kod będzie musiał być dość skomplikowany, aby obsłużyć tę konfigurację. Kiedy chcę przetasować karty, muszę przetasować pozycje w tej samej kolejności. Co się stanie, jeśli całkowicie wyjmę kartę z talii? Będę musiał także zająć jego stanowisko i umieścić je gdzie indziej.
A jeśli chcę przechowywać jeszcze więcej informacji o kartach? Co jeśli chcę zapisać, czy każda karta jest odwrócona? Co jeśli chcę jakiegoś silnika fizyki i muszę znać prędkość kart? Potrzebuję kolejnej listy do przechowywania grafiki dla każdej karty! I dla wszystkich tych punktów danych potrzebuję osobnego kodu, aby wszystkie były odpowiednio zorganizowane, więc każda karta w jakiś sposób odwzorowuje wszystkie swoje dane!
Teraz spróbujmy to na OOP.
Zamiast listy kodów zdefiniujmy klasę Card i stwórzmy z niej listę obiektów Card.
Teraz nagle wszystko jest znacznie prostsze. Jeśli chcę przesunąć kartę, nie muszę ustalać, gdzie jest ona w talii, a następnie użyj jej, aby uzyskać jej pozycję z szeregu pozycji. Muszę tylko powiedzieć
thecard.pos=newpos
. Kiedy wyjmuję kartę z listy głównej talii, nie muszę tworzyć nowej listy do przechowywania wszystkich innych danych; kiedy obiekt karty się porusza, wszystkie jego właściwości poruszają się wraz z nim. A jeśli chcę karty, która zachowuje się inaczej po odwróceniu, nie muszę modyfikować funkcji odwracania w moim głównym kodzie, aby wykrywał te karty i zachowywał się inaczej; Muszę tylko podklasować kartę i modyfikować funkcję flip () w podklasie.Ale nic, co tam zrobiłem, nie byłoby możliwe bez OO. Po prostu z językiem zorientowanym obiektowo, język wykonuje wiele pracy, aby utrzymać wszystko razem, co oznacza, że masz znacznie mniejszą szansę na popełnienie błędów, a Twój kod jest krótszy i łatwiejszy do odczytania i napisania.
Lub, podsumowując jeszcze bardziej, OO pozwala pisać prostsze z pozoru programy, które wykonują tę samą pracę, co programy bardziej złożone, ukrywając wiele typowych zawiłości obsługi danych za zasłoną abstrakcji.
źródło
Po kilku latach pisania wbudowanego C zarządzającego takimi urządzeniami, portami szeregowymi i pakietami komunikacyjnymi między portami szeregowymi, portami sieciowymi i serwerami; Znalazłem się, wyszkolony inżynier elektryk z ograniczonym doświadczeniem w programowaniu proceduralnym, wymyślając własne abstrakty ze sprzętu, który ostatecznie objawił się w tym, co później zrozumiałem, że to, co normalni ludzie nazywają programowaniem obiektowym.
Kiedy przeniosłem się na stronę serwera, byłem wystawiony na fabrykę, która ustawiała reprezentację obiektową każdego urządzenia w pamięci podczas tworzenia instancji. Z początku nie rozumiałem słów ani tego, co się działo - po prostu wiedziałem, że poszedłem do pliku o nazwie tak i tam i napisałem kod. Później znów odkryłem wartość OOP.
Ja osobiście uważam, że jest to jedyny sposób nauczenia orientacji obiektowej. Miałem klasę Intro to OOP (Java) na pierwszym roku i było to całkowicie nad moją głową. Opisy OOP oparte na klasyfikacji kociaka-> kota-> ssaka-> żywego-> rzeczy lub liścia-> gałęzi-> drzewa-> ogrodu są, moim skromnym zdaniem, całkowicie absurdalną metodologią, ponieważ nikt nigdy nie będzie próbował rozwiązać tych problemów problemy, jeśli można je nawet nazwać problemami ...
Myślę, że łatwiej jest odpowiedzieć na twoje pytanie, jeśli spojrzysz na to w sposób mniej bezwzględny - nie „co rozwiązuje”, ale bardziej z punktu widzenia „oto problem, a oto, jak to ułatwia”. W moim szczególnym przypadku portów szeregowych mieliśmy kilka kompilacji #ifdefs, które dodały i usuwały kod, który statycznie otwierał i zamykał porty szeregowe. Funkcje otwierania portów były wywoływane wszędzie i mogły być zlokalizowane w dowolnym miejscu w 100k liniach kodu systemu operacyjnego, który mieliśmy, a IDE nie wyszarzyło tego, co nie zostało zdefiniowane - trzeba było to wyśledzić ręcznie i nosić to w głowie. Nieuchronnie możesz mieć kilka zadań próbujących otworzyć dany port szeregowy, oczekując ich urządzenia na drugim końcu, a następnie żaden z napisanych kodów nie działa i nie możesz zrozumieć, dlaczego.
Abstrakcja była, choć wciąż w C, „klasą” portu szeregowego (no cóż, tylko typem struktury), którą mieliśmy tablicę - po jednym dla każdego portu szeregowego - i zamiast [ekwiwalentu DMA w portach szeregowych] Funkcje „OpenSerialPortA” „SetBaudRate” itp. Wywoływane bezpośrednio na sprzęcie z zadania, wywołaliśmy funkcję pomocniczą, do której przekazano wszystkie parametry komunikacyjne (baud, parzystość itp.), Która najpierw sprawdziła tablicę struktury, aby sprawdzić, czy to port został już otwarty - jeśli tak, to według którego zadania, które powiedziałby ci jako printf debugowania, abyś mógł natychmiast przejść do sekcji kodu, którą musisz wyłączyć - a jeśli nie, to przystąpił do ustawiania wszystkie parametry dzięki ich funkcjom montażu HAL i wreszcie otworzyły port.
Oczywiście istnieje również niebezpieczeństwo dla OOP. Kiedy w końcu posprzątałem tę bazę kodu i wszystko uporządkowałem i uporządkowałem - pisanie nowych sterowników dla tej linii produktów było w końcu możliwe do obliczenia, przewidywalne, mój kierownik EOL opracował produkt specjalnie, ponieważ był to o jeden projekt mniej potrzebny zarządzać, a on był na granicy usuwalnego średniego kierownictwa. Co mnie bardzo zabrało / bardzo mnie zniechęciło, więc rzuciłem pracę. LOL.
źródło
istnieje wiele twierdzeń i zamiarów dotyczących tego, co / gdzie programowanie OOP ma przewagę nad programowaniem proceduralnym, w tym przez jego wynalazców i użytkowników. ale tylko dlatego, że technologia została zaprojektowana w określonym celu przez jej projektantów, nie gwarantuje osiągnięcia tych celów. jest to kluczowe zrozumienie w dziedzinie inżynierii oprogramowania pochodzącej ze słynnego eseju Brooksa „No silver bullet”, które jest nadal aktualne pomimo rewolucji kodowania OOP. (zobacz także cykl Gypener Hype dla nowych technologii).
wielu, którzy skorzystali z obu, ma również opinie z niepotwierdzonego doświadczenia, a to ma pewną wartość, ale w wielu badaniach naukowych wiadomo, że samoocena analizy może być niedokładna. wydaje się, że niewiele jest ilościowej analizy tych różnic, a jeśli tak, to nie jest ona tak często cytowana. jest to nieco zdumiewające ilu komputerowych naukowcy mówić autorytatywnie na pewnych tematów centralnych do swojej dziedzinie jeszcze nie faktycznie przytoczyć badania naukowe na poparcie swoich poglądów i nie zdają sobie sprawy, że są faktycznie przekazując konwencjonalnej mądrości w swojej dziedzinie (choć powszechne ).
ponieważ jest to strona naukowa / forum, oto krótka próba postawienia licznych opinii na solidniejszych podstawach i oszacowania rzeczywistej różnicy. mogą istnieć inne badania i mam nadzieję, że inni mogą je wskazać, jeśli o nich usłyszą. (pytanie zen: jeśli naprawdę jest zasadnicza różnica, a tak duży wysiłek w dziedzinie inżynierii oprogramowania komercyjnego i gdzie indziej został włożony / zainwestowany w realizację tego, dlaczego naukowe dowody tego są tak trudne do zdobycia? jakieś klasyczne, często cytowane odniesienie w tej dziedzinie, które ostatecznie określa różnicę?)
ten artykuł wykorzystuje analizę eksperymentalną / ilościową / naukową i szczególnie potwierdza, że zrozumienie przez początkujących programistów jest ulepszone metodami kodowania OOP w niektórych przypadkach, ale w innych przypadkach było niejednoznaczne (w odniesieniu do rozmiarów programów). zauważ, że jest to tylko jedno z wielu / głównych twierdzeń na temat wyższości OOP, które jest podnoszone w innych odpowiedziach i przez zwolenników OOP. badanie prawdopodobnie mierzyło element psychologiczny zwany rozumieniem kodowania „obciążenia poznawczego / kosztów ogólnych” .
Porównanie rozumienia programów obiektowych i proceduralnych przez początkujących programistów wchodzących w interakcje z komputerami. Susan Wiedenbeck, Vennila Ramalingam, Suseela Sarasamma, Cynthia L Corritore (1999)
Zobacz też:
Jakie są zalety programowania obiektowego w porównaniu z programowaniem proceduralnym? programmers.se
Czy programowanie proceduralne ma jakąkolwiek przewagę nad OOP? przepełnienie stosu
źródło
Bądź ostrożny. Przeczytaj klasyk R. Kinga „Mój kot jest zorientowany obiektowo” w „Pojęciach zorientowanych obiektowo, bazach danych i aplikacjach” (Kim i Lochovsky, red.) (ACM, 1989). „Obiektowe” stało się bardziej modnym słowem niż jednoznaczną koncepcją.
Poza tym istnieje wiele odmian tematu, które niewiele mają wspólnego. Istnieją języki oparte na prototypach (dziedziczenie pochodzi od obiektów, nie ma klas jako takich) i języki oparte na klasach. Istnieją języki, które pozwalają na wielokrotne dziedziczenie, inne nie. Niektóre języki mają takie pojęcie, jak interfejsy Javy (można je traktować jako formę rozwodnionego wielokrotnego dziedziczenia). Istnieje pomysł na mixiny. Dziedziczenie może być raczej ścisłe (jak w C ++, tak naprawdę nie może tak naprawdę zmienić tego, co dostajesz w podklasie), lub bardzo swobodnie obsługiwane (w Perlu podklasa może przedefiniować prawie wszystko). Niektóre języki mają jeden katalog główny do dziedziczenia (zwykle nazywany Obiektem, z zachowaniem domyślnym), inne pozwalają programistowi na tworzenie wielu drzew. Niektóre języki twierdzą, że „wszystko jest przedmiotem”, inne obsługują obiekty i obiekty niebędące obiektami, niektóre (jak Java) mają „większość to obiekty, ale tych kilku typów tutaj nie ma”. Niektóre języki kładą nacisk na ścisłe enkapsulowanie stanu w obiektach, inne czynią go opcjonalnym (C ++ 'prywatny, chroniony, publiczny), inne nie mają wcale enkapsulacji. Jeśli spojrzysz na język taki jak Scheme pod odpowiednim kątem, zobaczysz, że OOP jest wbudowane bez specjalnego wysiłku (może zdefiniować funkcje zwracające funkcje, które zawierają pewien stan lokalny).
źródło
Mówiąc w skrócie Programowanie obiektowe rozwiązuje problemy bezpieczeństwa danych występujące w programowaniu proceduralnym. Odbywa się to za pomocą koncepcji enkapsulacji danych, umożliwiając dziedziczenie danych tylko przez uzasadnione klasy. Modyfikatory dostępu ułatwiają osiągnięcie tego celu. Mam nadzieję, że to pomoże :)
źródło