dziedziczenie prototypowe vs. klasowe

208

W JavaScript każdy obiekt jest jednocześnie instancją i klasą. Aby wykonać dziedziczenie, możesz użyć dowolnej instancji obiektu jako prototypu.

W Pythonie, C ++ itp. Istnieją klasy i instancje jako osobne pojęcia. Aby wykonać dziedziczenie, musisz użyć klasy podstawowej, aby utworzyć nową klasę, której można następnie użyć do utworzenia instancji pochodnych.

Dlaczego JavaScript poszedł w tym kierunku (orientacja obiektowa oparta na prototypach)? jakie są zalety (i wady) OO opartego na prototypach w porównaniu z tradycyjnym OO opartym na klasach?

Stefano Borini
źródło
10
JavaScript był pod wpływem Self, który był pierwszym językiem z dziedziczeniem prototypowym. W tym czasie dziedziczono klasyczne dziedzictwo, po raz pierwszy wprowadzone w Simula. Jednak klasyczne dziedzictwo było zbyt skomplikowane. Następnie David Ungar i Randall Smith mieli objawienie po przeczytaniu GEB - „Najbardziej specyficzne wydarzenie może służyć jako ogólny przykład klasy wydarzeń”. Zdali sobie sprawę, że klasy nie są wymagane do programowania obiektowego. Stąd narodziła się Jaźń. Aby dowiedzieć się, w jaki sposób dziedziczenie prototypowe jest lepsze niż dziedziczenie klasyczne, przeczytaj to: stackoverflow.com/a/16872315/783743 =)
Aadit M Shah
@AaditMShah Co / kto jest GEB?
Alex
3
@Alex GEB to książka napisana przez Douglasa Hofstadtera. To skrót od Gödel Escher Bach. Kurt Gödel był matematykiem. Escher był artystą. Bach był pianistą.
Aadit M Shah,

Odpowiedzi:

201

Jest tu około stu zagadnień terminologicznych, głównie wokół kogoś (nie ciebie), który stara się, aby ich pomysł brzmiał jak najlepszy.

Wszystkie języki obiektowe muszą być w stanie poradzić sobie z kilkoma pojęciami:

  1. enkapsulacja danych wraz z powiązanymi operacjami na danych, zwanymi między innymi elementami danych i funkcjami elementów, lub jako dane i metody.
  2. Dziedziczenie, możliwość stwierdzenia, że ​​te obiekty są takie same, jak inny zestaw obiektów Z WYJĄTKIEM tych zmian
  3. polimorfizm („wiele kształtów”), w którym obiekt sam decyduje, jakie metody mają zostać uruchomione, dzięki czemu można polegać na języku, aby poprawnie kierować żądania.

Teraz, jeśli chodzi o porównanie:

Pierwszą rzeczą jest całe pytanie „klasa” kontra „prototyp”. Pomysł pierwotnie rozpoczął się w Simula, gdzie za pomocą metody opartej na klasach każda klasa reprezentowała zestaw obiektów, które dzieliły tę samą przestrzeń stanu (czytaj „możliwe wartości”) i te same operacje, tworząc w ten sposób klasę równoważności. Jeśli spojrzysz wstecz na Smalltalk, ponieważ możesz otworzyć klasę i dodać metody, jest to faktycznie to samo, co możesz zrobić w JavaScript.

Później języki OO chciały mieć możliwość statycznego sprawdzania typów, więc otrzymaliśmy pojęcie ustalonego zestawu klas w czasie kompilacji. W wersji otwartej miałeś większą elastyczność; w nowszej wersji można było sprawdzić niektóre rodzaje poprawności w kompilatorze, które w innym przypadku wymagałyby testowania.

W języku „klasowym” kopiowanie odbywa się w czasie kompilacji. W języku prototypowym operacje są przechowywane w prototypowej strukturze danych, która jest kopiowana i modyfikowana w czasie wykonywania. Jednak abstrakcyjnie klasa jest nadal klasą równoważności wszystkich obiektów, które mają tę samą przestrzeń stanu i metody. Dodając metodę do prototypu, skutecznie tworzysz element nowej klasy równoważności.

Dlaczego to robisz? przede wszystkim dlatego, że tworzy prosty, logiczny i elegancki mechanizm w czasie wykonywania. teraz, aby utworzyć nowy obiekt lub nową klasę, wystarczy wykonać głęboką kopię, kopiując wszystkie dane i prototypową strukturę danych. Otrzymujesz dziedziczenie i polimorfizm mniej więcej za darmo: wyszukiwanie metody zawsze polega na pytaniu słownika o implementację metody według nazwy.

Powodem, dla którego skończyło się skryptem JavaScript / ECMA, jest to, że kiedy zaczynaliśmy 10 lat temu, mieliśmy do czynienia ze znacznie słabszymi komputerami i znacznie mniej zaawansowanymi przeglądarkami. Wybór metody opartej na prototypach oznaczał, że interpreter może być bardzo prosty, zachowując pożądane właściwości orientacji obiektowej.

Charlie Martin
źródło
1
Zgadza się, czy ten paragraf brzmi tak, jakbym miał na myśli inaczej? Dahl i Nyqvist wymyślili „klasę” jako zbiór rzeczy z tą samą sygnaturą metody.
Charlie Martin
1
Czy ta zmiana mówi, że lepiej?
Charlie Martin
2
Nie, przepraszam, CLOS pochodzi z końca lat 80. dreamsongs.com/CLOS.html Smalltalk z 1980 en.wikipedia.org/wiki/Smalltalk i Simula z pełną orientacją obiektową od 1967-68 en.wikipedia.org/wiki/Simula
Charlie Martin
3
@Stephano, Nie różnią się tak bardzo jak wszystko: Python, Ruby, Smalltalk używają słowników do wyszukiwania metod, a javascript i Self mają klasy. W pewnym stopniu można argumentować, że różnica polega tylko na tym, że języki zorientowane na prototypy ujawniają swoje implementacje. Prawdopodobnie więc dobrze jest nie robić z tego wielkiego problemu: prawdopodobnie bardziej przypomina to dyskusję między EMACS a vi.
Charlie Martin
21
Przydatna odpowiedź . +1 Mniej przydatne śmieci w komentarzach. To znaczy, czy ma to znaczenie, czy CLOS, czy Smalltalk były pierwsze? Większość ludzi i tak nie jest historykami.
Adam Arold
40

Porównanie, które jest nieco tendencyjne w stosunku do podejścia opartego na prototypach, można znaleźć w artykule Self: The Power of Simplicity . Artykuł przedstawia następujące argumenty za prototypami:

Tworzenie przez kopiowanie . Tworzenie nowych obiektów z prototypów odbywa się poprzez prostą operację, kopiowanie, przy użyciu prostej metafory biologicznej, klonowanie. Tworzenie nowych obiektów z klas odbywa się przez tworzenie instancji, które obejmuje interpretację informacji o formacie w klasie. Tworzenie instancji jest podobne do budowania domu z planu. Kopiowanie odwołuje się do nas jako prostsza metafora niż tworzenie.

Przykłady istniejących modułów . Prototypy są bardziej konkretne niż klasy, ponieważ są to przykłady obiektów, a nie opisy formatu i inicjalizacji. Te przykłady mogą pomóc użytkownikom w ponownym użyciu modułów, ułatwiając ich zrozumienie. System oparty na prototypach pozwala użytkownikowi zbadać typowego przedstawiciela, zamiast wymagać od niego sensownego opisu.

Obsługa jedynych w swoim rodzaju obiektów . Self zapewnia platformę, która z łatwością może zawierać jedyne w swoim rodzaju obiekty z własnym zachowaniem. Ponieważ każdy obiekt ma nazwane gniazda, a gniazda mogą zawierać stan lub zachowanie, każdy obiekt może mieć unikalne gniazda lub zachowanie. Systemy oparte na klasach są zaprojektowane do sytuacji, w których istnieje wiele obiektów o takim samym zachowaniu. Nie ma wsparcia językowego, aby obiekt posiadał własne unikalne zachowanie, i niewygodne jest stworzenie klasy, która ma zagwarantowaną tylko jedną instancję [ pomyśl singleton wzorzec ]. Jaźń nie cierpi na żadną z tych wad. Każdy obiekt można dostosować za pomocą własnego zachowania. Unikalny obiekt może przechowywać unikalne zachowanie i osobna „instancja” nie jest potrzebna.

Eliminacja meta-regresji . Żaden obiekt w systemie klasowym nie może być samowystarczalny; inny obiekt (jego klasa) jest potrzebny do wyrażenia jego struktury i zachowania. Prowadzi to do konceptualnie nieskończonej meta-regresji: a pointjest instancją klasy Point, która jest instancją metaklasy Point, która jest instancją metametaklasy Point, ad infinitum. Z drugiej strony w systemach opartych na prototypach obiekt może zawierać własne zachowanie; żaden inny przedmiot nie jest potrzebny, aby tchnąć w nie życie. Prototypy eliminują meta-regres.

Self jest prawdopodobnie pierwszym językiem do implementacji prototypów (był także pionierem innych interesujących technologii, takich jak JIT, które później trafiły do ​​JVM), więc czytanie innych dokumentów Self powinno być również pouczające.

Vijay Mathew
źródło
5
RE: Eliminacja meta-regresji: we wspólnym systemie obiektowym Lisp, który jest oparty na klasach, a pointjest instancją klasy Point, która jest instancją metaklasy standard-class, która jest instancją samej siebie, ad finitum.
Max Nanasy,
Linki do samodzielnych dokumentów są martwe. Działające linki: Self: The Power of Simplicity | Własna bibliografia
użytkownik1201917
24

Powinieneś sprawdzić świetną książkę o JavaScript autorstwa Douglasa Crockforda . Zapewnia bardzo dobre wyjaśnienie niektórych decyzji projektowych podjętych przez twórców JavaScript.

Jednym z ważnych aspektów projektowania JavaScript jest prototypowy system dziedziczenia. Obiekty są pierwszorzędnymi obywatelami JavaScript, do tego stopnia, że ​​zwykłe funkcje są również implementowane jako obiekty (a dokładniej obiekt „Funkcja”). Moim zdaniem, kiedy pierwotnie został zaprojektowany do działania w przeglądarce, miał służyć do tworzenia wielu obiektów singletonowych. W przeglądarce DOM znajdziesz to okno, dokument itp. Wszystkie pojedyncze obiekty. JavaScript to także dynamiczny język luźno wpisany (w przeciwieństwie do silnie napisanego języka Python, język dynamiczny), w wyniku czego za pomocą właściwości „prototyp” zaimplementowano koncepcję rozszerzenia obiektu.

Myślę więc, że istnieją pewne zalety dla OO opartego na prototypach, zaimplementowanego w JavaScript:

  1. Odpowiedni w środowiskach o luźnym typie, nie ma potrzeby definiowania wyraźnych typów.
  2. Sprawia, że ​​niezwykle łatwo jest wdrożyć wzorzec singletonu (porównaj JavaScript i Java pod tym względem, a będziesz wiedział, o czym mówię).
  3. Zapewnia sposoby zastosowania metody obiektu w kontekście innego obiektu, dynamicznego dodawania i zastępowania metod z obiektu itp. (Rzeczy, które nie są możliwe w silnie typowanych językach).

Oto niektóre z wad prototypowego OO:

  1. Nie ma łatwego sposobu na implementację zmiennych prywatnych. Jego możliwe do wykonania przy użyciu prywatnych vars Crockforda czarów „s za pomocą zamknięć , ale jego na pewno nie tak trywialne, jak za pomocą zmiennych prywatnych powiedzmy Java lub C #.
  2. Nie wiem jeszcze, jak zaimplementować wielokrotne dziedziczenie (o ile jest to warte) w JavaScript.
Amit
źródło
2
Wystarczy użyć konwencji nazewnictwa dla prywatnych zmiennych, tak jak Python.
aehlke,
1
w js sposób na prywatne vars polega na zamknięciach i jest to niezależne od wybranego typu dziedziczenia.
Benja,
6
Crockford zrobił wiele, by zniszczyć JavaScript, ponieważ dość prosty język skryptowy został przekształcony w mistrzowską fascynację jego elementami wewnętrznymi. JS nie ma prawdziwego zakresu prywatnego słowa kluczowego ani prawdziwego wielokrotnego dziedziczenia: nie próbuj ich fałszować.
Hal50000