Czym właściwie dziedziczenie prototypowe różni się od dziedziczenia klasycznego?

27

Dziedziczenie, polimorfizm i enkapsulacja to trzy najbardziej wyraźne, ważne cechy OOP, a od nich dziedziczenie ma obecnie wysoką statystykę użytkowania. Uczę się JavaScript, a tutaj wszyscy mówią, że ma on dziedzictwo prototypowe, a ludzie na całym świecie mówią, że jest to coś zupełnie innego niż klasyczne dziedzictwo.

Nie rozumiem jednak, jaka jest ich różnica z praktycznego punktu widzenia? Innymi słowy, kiedy zdefiniujesz klasę bazową (prototyp), a następnie wyprowadzisz z niej niektóre podklasy, oboje masz dostęp do funkcjonalności swojej klasy bazowej i możesz rozszerzyć funkcje na klasy pochodne. Jeśli weźmiemy pod uwagę to, co powiedziałem, jako zamierzony wynik dziedziczenia, to dlaczego powinniśmy się przejmować, jeśli używamy wersji prototypowej lub klasycznej?

Aby wyjaśnić siebie bardziej, nie widzę różnicy w zakresie użyteczności i wzorców użytkowania prototypowego i klasycznego dziedziczenia. Powoduje to, że nie jestem zainteresowany dowiedzeniem się, dlaczego się różnią, ponieważ oba skutkują tym samym, OOAD. Czym praktycznie (nie teoretycznie) dziedziczenie prototypowe różni się od dziedziczenia klasycznego?

Saeed Neamati
źródło

Odpowiedzi:

6

Ostatni post na blogu o JS OO

Uważam, że porównujesz klasyczną emulację OO w JavaScript i klasyczną OO i oczywiście nie widać żadnej różnicy.

Zastrzeżenie: zamień wszystkie odniesienia do „prototypowego OO” na „prototypowy OO w JavaScript”. Nie znam specyfiki Self ani żadnej innej implementacji.

Jednak prototypowy OO jest inny. Dzięki prototypom masz tylko obiekty i możesz tylko wstrzykiwać obiekty do łańcucha prototypów innych obiektów. Ilekroć uzyskujesz dostęp do właściwości obiektu, przeszukujesz ten obiekt i dowolne obiekty w łańcuchu prototypów.

W przypadku prototypowego OO nie ma pojęcia enkapsulacji. Hermetyzacja jest cechą zakresu, zamknięć i funkcji pierwszej klasy, ale nie ma nic wspólnego z prototypowym OO. Nie ma też pojęcia dziedziczenia, to, co ludzie nazywają „dziedziczeniem”, jest po prostu polimorfizmem.

Ma jednak polimorfizm.

Na przykład

var Dog = {
  walk: function() { console.log("walks"); }
}

var d = Object.create(Dog);
d.walk();

Wyraźnie dma dostęp do tej metody Dog.walki wykazuje polimorfizm.

Tak naprawdę jest duża różnica . Masz tylko polimorfizm.

Jak jednak wspomniano, jeśli chcesz to zrobić (nie mam pojęcia, dlaczego), możesz emulować klasyczne OO w JavaScript i mieć dostęp do (ograniczonej) enkapsulacji i dziedziczenia.

Raynos
źródło
1
@Rayons, Google dla dziedziczenia prototypowego, a zobaczysz, że pojawi się dziedziczenie prototypowe . Nawet od Yahoo!
Saeed Neamati,
@Seeed dziedziczenie jest niejasnym terminem i jest często nadużywane. Co oznaczają „dziedziczenie”, nazywam „polimorfizm”. Definicje są zbyt niejasne.
Raynos,
Nie @ Rayons, miałem na myśli, że słowo prototypowe jest poprawnym terminem, a nie prototypowym . Nie mówiłem o dziedziczeniu :)
Saeed Neamati,
1
@ Tak, to jedna z tych literówek, które mój umysł po prostu wygasają. Nie zwracam na to uwagi
Raynos
1
Hmm .. terminologia. ick. Nazwałbym sposób, w jaki obiekty javascript są powiązane z ich prototypami, „delegacją”. Wydaje mi się, że polimorfizm jest problemem niższego poziomu, ponieważ dana nazwa może wskazywać na inny kod dla różnych obiektów.
Sean McMillan,
15

Dziedziczenie klasyczne dziedziczy zachowanie, bez żadnego stanu, od klasy nadrzędnej. Dziedziczy zachowanie w momencie tworzenia instancji obiektu.

Dziedziczenie prototypowe dziedziczy zachowanie i stan z obiektu nadrzędnego. Dziedziczy zachowanie i stan w momencie wywołania obiektu. Gdy obiekt nadrzędny zmienia się w czasie wykonywania, wpływa to na stan i zachowanie obiektów potomnych.

„Zaletą” dziedziczenia prototypowego jest to, że można „załatać” stan i zachowanie po utworzeniu instancji wszystkich obiektów. Na przykład w frameworku Ext JS często ładuje się „przesłonięcia”, które łatają główne komponenty frameworku po utworzeniu instancji frameworka.

Joeri Sebrechts
źródło
4
W praktyce, jeśli dziedziczysz stan, ustawiasz się na świecie bólu. Stan odziedziczony zostanie udostępniony, jeśli będzie żył na innym obiekcie.
Sean McMillan,
1
Przyszło mi do głowy, że w javascript naprawdę nie ma pojęcia zachowania odrębnego od stanu. Oprócz funkcji konstruktora wszystkie zachowania można przypisywać / modyfikować po utworzeniu obiektu, tak jak każdy inny stan.
Joeri Sebrechts
Więc Python nie ma klasycznego dziedzictwa? Jeśli mam class C(object): def m(self, x): return x*2i instance = C()wtedy gdy uruchamiam instance.m(3)otrzymuję 6. Ale gdybym wtedy zmienić Ctak C.m = lambda s, x: x*xi biegnę instance.m(3)I teraz dostać 9. To samo dzieje się, jeśli zrobię a class D(C)i zmienię metodę, Ca następnie wszelkie przypadki Dotrzymania zmienionej metody również. Czy mam nieporozumienie, czy to oznacza, że ​​Python nie ma klasycznego dziedzictwa zgodnie z twoją definicją?
mVChr
@mVChr: ​​Nadal dziedziczysz zachowanie, a nie podajesz w tym przypadku, co wskazuje na klasyczne dziedziczenie, ale wskazuje na problem z moją definicją „dziedziczy zachowanie w momencie utworzenia obiektu”. Nie jestem pewien, jak poprawić definicję.
Joeri Sebrechts,
9

Po pierwsze: przez większość czasu będziesz używać obiektów, a nie ich definiować, a używanie obiektów jest takie samo w obu paradygmatach.

Po drugie: Większość środowisk prototypowych używa tego samego rodzaju podziału co środowiska klasowe - zmienne dane instancji z odziedziczonymi metodami. Więc znów jest bardzo mała różnica. (Zobacz moją odpowiedź na to pytanie o przepełnienie stosu oraz programy samoorganizacji bez klas . Zajrzyj do Citeseer, aby uzyskać wersję PDF .)

Po trzecie: Dynamiczna natura Javascript ma znacznie większy wpływ niż dziedziczenie. Fakt, że mogę dodać nową metodę do wszystkich instancji typu, przypisując ją do obiektu podstawowego, jest zgrabny, ale mogę zrobić to samo w Ruby, ponownie otwierając klasę.

Po czwarte: Praktyczne różnice są niewielkie, podczas gdy praktyczna kwestia zapominania o użyciu newjest znacznie większa - to znaczy, że znacznie bardziej prawdopodobne jest, że będziesz odczuwać brakujące newniż różnicę między kodem prototypowym a klasycznym .

Wszystko, co powiedziano, praktyczną różnicą między dziedziczeniem prototypowym a klasycznym jest to, że twoje rzeczy, które przechowują metody (klasy) są takie same, jak twoje rzeczy, które przechowują dane (instancje). Oznacza to, że możesz budować swoje klasy fragmentarycznie, przy użyciu tych samych narzędzi do manipulacji obiektami, których można użyć w dowolnej instancji. (Tak właśnie robią wszystkie biblioteki emulujące klasy. Aby uzyskać podejście nie do końca podobne do innych, zajrzyj na Traits.js ). Jest to szczególnie interesujące w przypadku metaprogramowania.

Sean McMillan
źródło
tak, najlepsza odpowiedź IMO!
Aivar
0

Dziedziczenie prototypowe w JavaScript różni się od klas pod następującymi ważnymi względami:

Konstruktory to po prostu funkcje, które można wywoływać bez new:

function Circle (r, x, y) { 
  //stuff here
}
Var c = new Circle();
Circle.call(c, x, y, z); //This works and you can do it over and over again.

Nie ma prywatnych zmiennych ani metod, w najlepszym wypadku możesz to zrobić:

function Circle (r, x, y) {
  var color = 'red';
  function drawCircle () {
    //some code here with x, y and r
  }
  drawCircle();
  this.setX = function (x_) {
    x = x_;
    drawCircle();
  }

}
Circle.prototype.getX = function () {
   //Can't access x!
}

W poprzednim przykładzie nie można znacząco rozszerzyć klasy, jeśli uciekamy się do fałszywych zmiennych prywatnych i metod, a ponadto wszelkie zadeklarowane metody publiczne będą odtwarzane przy każdym utworzeniu nowej instancji.

Bjorn
źródło
Możesz znacznie rozszerzyć „klasę”. Po prostu nie można uzyskać dostęp do zmiennych lokalnych color, r, x, yi drawCirclektóre są związane z zakresem leksykalnymCircle
Raynos
To prawda, możesz, ale nie możesz tego zrobić bez utworzenia szeregu „publicznych” metod dodawanych do kontekstu, które są tworzone za każdym razem, gdy tworzysz instancję.
Bjorn,
Używasz tam bardzo dziwnego wzoru. Circle.call()jako konstruktor? Wygląda na to, że próbujesz opisać „obiekty funkcjonalne” (mylne określenie ...)
Sean McMillan
To nie jest coś, co kiedykolwiek zrobiłbym, po prostu mówię, że można wywoływać konstruktor w kółko za pomocą JavaScript, ale nie w językach z tradycyjnymi klasami.
Bjorn,
1
Ale w jaki sposób te różnice są „ważne”? Nie widzę budowania obiektów przez wywoływanie funkcji, nie używanie słowa „new” jako „ważnej”, tylko niewielka różnica w składni. Podobnie brak prywatnych zmiennych nie jest zasadniczo różny, tylko niewielka różnica. Dodatkowo możesz mieć (coś podobnego do) zmiennych prywatnych w sposób, w jaki opisujesz.
Roel