Wydaje się, że C ma swoje quasi-obiekty, takie jak „struktury”, które można uznać za obiekty (w sposób, w jaki normalnie myślimy).
A także same pliki C są w zasadzie osobnymi „modułami”, prawda? Czy zatem moduły nie są też czymś w rodzaju „obiektów”? Jestem zdezorientowany, dlaczego C, który wydaje się tak podobny do C ++, jest uważany za język „proceduralny” niskiego poziomu, podczas gdy C ++ jest „obiektowym” wysokiego poziomu
* edycja: (wyjaśnienie) dlaczego i gdzie jest narysowana linia, czym jest „obiekt”, a czym nie jest?
object-oriented
procedural
Mroczny Templariusz
źródło
źródło
Odpowiedzi:
Przyjrzyjmy się razem z tobą na stronie Wikipedii poświęconej programowaniu obiektowemu i sprawdźmy cechy struktur typu C, które odpowiadają temu, co tradycyjnie uważa się za styl obiektowy:
Czy struktury C składają się z pól i metod wraz z ich interakcjami ? Nie.
Czy struktury C wykonują którąkolwiek z tych rzeczy w sposób „pierwszej klasy”? Nie. Język działa przeciwko tobie na każdym kroku.
Czy struktury C to robią? Nie.
Czy struktury C to robią? Tak.
Nie.
Czy sama struktura może wysyłać i odbierać wiadomości? Nie. Czy może przetwarzać dane? Nie.
Czy dzieje się tak w C? Nie.
Czy którakolwiek z tych cech struktur C? Nie.
Jak myślisz, które cechy struktur są „obiektowe”? Bo nie mogę znaleźć dowolny inny niż fakt, że kodowanym określenia rodzajów .
Teraz oczywiście możesz tworzyć struktury, które mają pola wskazujące funkcje. Możesz utworzyć struktury posiadające pola, które są wskaźnikami do tablic wskaźników funkcji, odpowiadającymi wirtualnym tabelom metod. I tak dalej. Oczywiście możesz emulować C ++ w C. Ale to jest bardzo nie idiomatyczny sposób programowania w C; lepiej byłoby po prostu używać C ++.
Ponownie, jakie cechy modułów uważasz, że działają one jak obiekty? Czy moduły obsługują abstrakcję, enkapsulację, przesyłanie wiadomości, modułowość, polimorfizm i dziedziczenie?
Abstrakcja i enkapsulacja są dość słabe. Oczywiście moduły są modułowe; dlatego nazywane są modułami. Wiadomości? Tylko w tym sensie, że wywołanie metody jest komunikatem, a moduły mogą zawierać metody. Wielopostaciowość? Nie. Dziedzictwo? Nie. Moduły są dość słabymi kandydatami na „obiekty”.
źródło
Kluczowe słowo to „zorientowane”, a nie „obiekt”. Nawet kod C ++, który używa obiektów, ale używa ich jak struktur, nie jest zorientowany obiektowo .
Zarówno C, jak i C ++ mogą wykonywać OOP (oprócz braku kontroli dostępu w C), ale składnia do robienia tego w C jest niewygodna (co najmniej), podczas gdy składnia w C ++ sprawia, że jest bardzo zachęcająca. C jest zorientowany na procedury, podczas gdy C ++ jest zorientowany na obiekty, pomimo prawie identycznych podstawowych możliwości w tym zakresie.
Kod, który wykorzystuje obiekty do implementacji projektów, które można wykonać tylko z obiektami (zwykle oznacza wykorzystanie polimorfizmu) jest kodem obiektowym. Kod, który używa obiektów jako niewiele więcej niż worków danych, nawet używając dziedziczenia w języku zorientowanym obiektowo, jest tak naprawdę tylko kodem proceduralnym, który jest bardziej skomplikowany, niż powinien. Kod w C, który używa wskaźników funkcji, które są zmieniane w czasie wykonywania z strukturami pełnymi danych, jest rodzajem polimorfizmu i można powiedzieć, że jest „obiektowy”, nawet w języku zorientowanym proceduralnie.
źródło
W oparciu o zasady najwyższego poziomu:
Obiekt jest enkapsulacją danych i zachowania w połączony ze sobą sposób, dzięki czemu działają one jako całość, które mogą być tworzone wielokrotnie i działały jako czarna skrzynka, jeśli znasz interfejs zewnętrzny.
Struktury zawierają dane, ale nie zachowują się, dlatego nie można ich uważać za obiekty.
Moduły zawierają zarówno zachowanie, jak i dane, ale nie są hermetyzowane w taki sposób, że oba są ze sobą powiązane i na pewno nie można ich wielokrotnie utworzyć.
I to zanim przejdziesz do dziedziczenia i polimorfizmu ...
źródło
„struktury” to tylko dane. Zwykle szybki i brudny test „orientacji obiektowej” brzmi: „Czy istnieje struktura, która umożliwia enkapsulację kodu i danych jako pojedynczej jednostki?”. C nie spełnia tego warunku i dlatego ma charakter proceduralny. C ++ przechodzi ten test.
źródło
this
wskaźnika, ale w takim przypadku dane i środki do przetwarzania danych zostaną zamknięte w strukturze.#include
byłyby do niego, a minus nic nie jest usuwane przez warunkowo włączone (#if
,#ifdef
i tym podobne).C, podobnie jak C ++, ma zdolność zapewniania abstrakcji danych , która jest jednym z idiomów istniejącego przed nią paradygmatu programowania obiektowego.
OOP w C ++ rozszerza środki na dane abstrakcyjne. Niektórzy twierdzą, że jest szkodliwy , podczas gdy inni uważają to za dobre narzędzie, jeśli są właściwie używane.
Znajdziesz jednak wielu „hakerów” głoszących o tym, że C doskonale potrafi uzyskać odpowiednią ilość abstrakcji i jak narzut stworzony przez C ++ tylko odwraca ich uwagę od rozwiązania rzeczywistego problemu.
Inni patrzą na to w bardziej zrównoważony sposób, akceptując zarówno zalety, jak i wady.
źródło
Musisz spojrzeć na drugą stronę monety: C ++.
W OOP myślimy o abstrakcyjnym obiekcie (i odpowiednio projektujemy program), na przykład samochodzie, który może się zatrzymać, przyspieszyć, skręcić w lewo lub w prawo itp. Struktura z pakietem funkcji po prostu nie pasuje do tej koncepcji.
W przypadku „prawdziwych” obiektów musimy na przykład ukryć członków, lub możemy też odziedziczyć dziedzictwo z prawdziwą relacją „to” i wiele innych.
PO PRZECZYTANIU PONIŻSZYCH KOMENTARZÓW: Dobrze, że (prawie) wszystko można zrobić za pomocą C (to zawsze prawda), ale na pierwszy rzut oka pomyślałem, że to, co odróżnia c od c ++, jest sposobem myślenia podczas projektowania programu.
Jedyne, co naprawdę robi różnicę, to narzucanie zasad przez kompilator . tj. czysta funkcja wirtualna i tak dalej. ale ta odpowiedź dotyczy wyłącznie problemów technicznych, ale myślę, że główną różnicą (jak wspomniano) jest oryginalny sposób myślenia podczas pisania kodu, ponieważ C ++ zapewnia lepszą wbudowaną składnię do robienia takich rzeczy, zamiast wykonywania OOP w nieco niezdarny sposób w C.
źródło
W pewnym sensie sam to powiedziałeś. Podczas gdy C ma rzeczy, które są czymś w rodzaju obiektów, nadal nie są obiektami i dlatego C nie jest uważany za język OOP.
źródło
Zorientowany obiektowo odnosi się zarówno do wzorca architektonicznego (lub nawet meta-wzorca), jak i języków, które mają funkcje ułatwiające wdrożenie lub zlecenie przy użyciu tego wzorca.
Możesz wdrożyć projekt „OO” (pulpit Gnome jest chyba najlepszym przykładem OO wykonanego w czystym C), widziałem to nawet w COBOL!
Jednak możliwość implementacji dawki projektowej OO nie czyni języka OO. Puryści twierdzą, że Java i C ++ nie są tak naprawdę OO, ponieważ nie można zastąpić ani odziedziczyć podstawowych „typów”, takich jak „int” i „char”, a Java nie obsługuje wielokrotnego dziedziczenia. Ponieważ jednak są one najczęściej używanymi językami OO i obsługują większość paradygmatu, większość „prawdziwych” programistów, którzy otrzymują wynagrodzenie za tworzenie działającego kodu, traktuje je jako języki OO.
Z drugiej strony C obsługuje tylko struktury (podobnie jak COBOL, Pascal i dziesiątki innych języków proceduralnych), można argumentować, że obsługuje wielokrotne dziedziczenie, ponieważ można użyć dowolnej funkcji na dowolnym fragmencie danych, ale większość uważa to za błąd niż funkcja.
źródło
Spójrzmy na definicję OO:
C nie zapewnia żadnej z tych trzech. W szczególności nie zapewnia przesyłania wiadomości , co jest najważniejsze.
źródło
static
funkcje wewnętrzne ( ) i elementy danych.Istnieje wiele kluczowych składników do OO, ale najważniejsze to, że większość kodu nie wie, co znajduje się w obiekcie (widzą interfejs powierzchni, a nie implementację), że stan obiektu jest jednostką zarządzaną (tj. gdy obiekt przestaje być, podobnie jak jego stan), a gdy jakiś kod wywołuje operację na obiekcie, robią to, nie wiedząc dokładnie, co to za operacja lub z nią wiąże (wszystko, co robią, to kierują się wzorem „Wiadomość” nad ścianą).
C dobrze kapsułkuje; kod, który nie widzi definicji struktury, nie może (legalnie) zaglądać do niej. Wszystko, co musisz zrobić, to umieścić taką definicję w pliku nagłówkowym:
Oczywiście będzie potrzeba funkcji, która buduje
Foo
s (tj. Fabrykę) i która powinna delegować część pracy do samego przydzielonego obiektu (tj. Metodą „konstruktora”), a także mieć sposób ponownego pozbywania się obiektu (pozwalając mu oczyścić go metodą „destruktora”), ale to są szczegóły.Wysyłanie metod (tj. Przesyłanie komunikatów) można również realizować zgodnie z konwencją, że pierwszy element struktury jest tak naprawdę wskaźnikiem do struktury pełnej wskaźników funkcji i że każdy z tych wskaźników funkcji musi przyjąć
Foo
swój pierwszy argument. Dispatch staje się wtedy kwestią wyszukania funkcji i wywołania jej z poprawnym argumentem przepisania, co nie jest takie trudne z makrem i trochę sprytne. (Ta tabela funkcji jest rdzeniem tego, czym tak naprawdę jest klasa w języku takim jak C ++.)Co więcej, daje to również późne wiązanie: cały kod wysyłki wie, że wywołuje określone przesunięcie w tabeli wskazywanej przez obiekt. Należy to ustawić tylko podczas przydzielania i inicjowania obiektu. Możliwe jest skorzystanie z jeszcze bardziej złożonych schematów wysyłkowych, które zwiększają dynamikę działania (kosztem prędkości), ale są czereśniami oprócz podstawowego mechanizmu.
Ale to nie znaczy, że C jest językiem OO. Kluczem jest to, że C pozostawia cię do wykonania całej trudnej pracy polegającej na samodzielnym napisaniu konwencji i mechanizmu wysyłania (lub skorzystaniu z biblioteki innej firmy). To dużo pracy. Nie zapewnia również wsparcia składniowego ani semantycznego, więc wdrożenie systemu pełnej klasy (z takimi rzeczami jak dziedziczenie) byłoby niepotrzebnie bolesne; jeśli masz do czynienia ze złożonym problemem, który jest dobrze opisany przez model OO, język OO będzie bardzo przydatny przy pisaniu rozwiązania. Dodatkową złożoność można uzasadnić.
źródło
Myślę, że C jest w porządku i przyzwoity do wdrażania koncepcji obiektowych, wzruszając ramionami . Większość różnic, jakie widzę między podzbiorem wspólnych mianowników języków uważanych za obiektowe, są niewielkie i mają charakter składniowy z mojego rodzaju pragmatycznego punktu widzenia.
Zacznijmy od, powiedzmy, ukrywania informacji. W C możemy to osiągnąć po prostu ukrywając definicję struktury i pracując z nią przez nieprzejrzyste wskaźniki. Który skutecznie modeluje
public
vs.private
rozróżnienie pól danych, ponieważ mamy z klas. Jest to dość łatwe i mało antyidiomatyczne, ponieważ standardowa biblioteka C polega na tym w znacznej mierze w celu ukrycia informacji.Oczywiście tracisz możliwość łatwego kontrolowania, gdzie struktura jest przydzielona w pamięci za pomocą nieprzezroczystych typów, ale to tylko godna uwagi różnica między, powiedzmy, C i C ++. C ++ jest zdecydowanie lepszym narzędziem do porównywania jego zdolności do programowania obiektowych pojęć nad C przy jednoczesnym zachowaniu kontroli nad układami pamięci, ale to niekoniecznie oznacza, że Java lub C # jest lepszy od C pod tym względem, ponieważ te dwa sprawiają, że jesteś całkowicie tracą możliwość kontrolowania, gdzie obiekty są przydzielane w pamięci.
I musimy użyć składni takiej
fopen(file, ...); fclose(file);
jak w przeciwieństwie dofile.open(...); file.close();
dużego whoop. Kogo to obchodzi? Może po prostu ktoś, kto mocno opiera się na autouzupełnianiu w swoim IDE. Przyznaję, że może to być bardzo przydatna funkcja z praktycznego punktu widzenia, ale może nie taka, która wymaga dyskusji na temat tego, czy język jest odpowiedni dla OOP.Brakuje nam możliwości skutecznego wdrażania
protected
pól. Całkowicie się tam złożę. Ale nie sądzę, aby istniała konkretna zasada, która mówi: „ Wszystkie języki OO powinny mieć funkcję umożliwiającą dostęp do podklas członkom klasy podstawowej, do których normalni klienci nadal nie powinni mieć dostępu ”. Poza tym rzadko widuję przypadki użycia dla chronionych członków, którzy nie są co najmniej trochę podejrzliwi, aby stać się przeszkodą w utrzymaniu.I oczywiście mamy do „emulować” oo polimorfizmu z tabelami wskaźników funkcji i wskaźniki do nich do dynamicznego wysłania trochę więcej boilerplate zainicjować te analogiczna
vtables
avptrs
, ale trochę boilerplate nigdy nie sprawiało mi wiele smutku.Dziedziczenie jest bardzo podobne. Możemy łatwo modelować to poprzez kompozycję, a przy wewnętrznym działaniu kompilatorów sprowadza się to do tego samego. Oczywiście tracimy bezpieczeństwo typu, jeśli chcemy obniżyć , i powiedziałbym, że jeśli chcesz być w ogóle obniżony , nie używaj do tego C, ponieważ rzeczy, które ludzie robią w C, aby emulować obniżanie, mogą być przerażające z punktu widzenia bezpieczeństwa, ale wolałbym, żeby ludzie wcale nie byli przygnębieni . Bezpieczeństwo typu jest czymś, czego łatwo można przeoczyć w C, ponieważ kompilator zapewnia tyle swobody interpretacji rzeczy jak tylko bity i bajty, poświęcając zdolność do wychwytywania potencjalnych błędów w czasie kompilacji, ale niektóre języki uważane za obiektowe nie są są nawet wpisywane statycznie.
Więc nie, myślę, że jest w porządku. Oczywiście nie użyłbym C, aby stworzyć bazę kodu na dużą skalę, która byłaby zgodna z zasadami SOLID, ale niekoniecznie z powodu jej niedociągnięć w froncie zorientowanym obiektowo. Wiele funkcji, których brakowałbym, gdybym próbował użyć C do takiego celu, byłyby związane z funkcjami języka, które nie są bezpośrednio uważane za warunek wstępny dla OOP, takie jak silne bezpieczeństwo typu, destruktory, które są automatycznie wywoływane, gdy obiekty wykraczają poza zakres, operator przeciążenie, szablony / ogólne i obsługa wyjątków. Właśnie wtedy brakuje mi dodatkowych funkcji, które sięgam po C ++.
źródło
foo_create
ifoo_destroy
afoo_clone
, npstruct Foo
takim przypadku musimy jedynie zachować niezmienniki, aby upewnić się, że dostęp do jego członków danych nie będzie możliwy i zmutowany przez świat zewnętrzny, który typy nieprzezroczyste (zadeklarowane, ale nie zdefiniowane dla świata zewnętrznego) natychmiast dają. Jest to prawdopodobnie silniejsza forma ukrywania informacji, na równi z tąpimpl
w C ++.Niezłe pytanie.
Jeśli zamierzasz nazwać język OO, musisz zadzwonić prawie do wszystkich języków proceduralnych OO. Czyni to termin bez znaczenia. c nie obsługuje języka dla OO. Jeśli ma struktury, ale struktury
types
nie są klasami.W rzeczywistości c nie ma wiele funkcji w porównaniu do większości języków. Jest używany głównie ze względu na szybkość, prostotę, popularność i wsparcie, w tym mnóstwo bibliotek.
źródło