eredità basata sul prototipo vs. basata sulla classe


208

In JavaScript, ogni oggetto è allo stesso tempo un'istanza e una classe. Per eseguire l'ereditarietà, è possibile utilizzare qualsiasi istanza di oggetto come prototipo.

In Python, C ++, ecc. Ci sono classi e istanze, come concetti separati. Per eseguire l'ereditarietà, è necessario utilizzare la classe base per creare una nuova classe, che può quindi essere utilizzata per produrre istanze derivate.

Perché JavaScript è andato in questa direzione (orientamento dell'oggetto basato su prototipo)? quali sono i vantaggi (e gli svantaggi) delle OO basate su prototipi rispetto alle OO tradizionali di classe?


10
JavaScript è stato influenzato da Self, che è stata la prima lingua con eredità prototipale. A quel tempo l'eredità classica era di gran moda, introdotta per la prima volta in Simula. Tuttavia l'eredità classica era troppo complicata. Quindi David Ungar e Randall Smith hanno avuto un'epifania dopo aver letto GEB - "L'evento più specifico può servire come esempio generale di una classe di eventi". Si sono resi conto che le classi non sono richieste per la programmazione orientata agli oggetti. Quindi il Sé è nato. Per sapere come prototipale eredità è meglio di eredità classica leggere questo: stackoverflow.com/a/16872315/783743 =)
Aadit M Shah

@AaditMShah Cosa / chi è GEB?
Alex,

3
@Alex GEB è ​​un libro scritto da Douglas Hofstadter. È un'abbreviazione di Gödel Escher Bach. Kurt Gödel era un matematico. Escher era un artista. Bach era un pianista.
Aadit M Shah,

Risposte:


201

Ci sono circa un centinaio di problemi terminologici qui, per lo più costruiti attorno a qualcuno (non tu) che cerca di far sembrare la sua idea come The Best.

Tutti i linguaggi orientati agli oggetti devono essere in grado di gestire diversi concetti:

  1. incapsulamento dei dati insieme ad operazioni associate sui dati, variamente conosciute come membri e funzioni dei membri, o come dati e metodi, tra le altre cose.
  2. eredità, la capacità di dire che questi oggetti sono proprio come quell'altro insieme di oggetti TRANNE questi cambiamenti
  3. polimorfismo ("molte forme") in cui un oggetto decide autonomamente quali metodi devono essere eseguiti, in modo che tu possa dipendere dal linguaggio per indirizzare correttamente le tue richieste.

Ora, per quanto riguarda il confronto:

La prima cosa è l'intera domanda "di classe" vs "prototipo". L'idea era originariamente iniziata in Simula, dove con un metodo basato su classi ogni classe rappresentava un insieme di oggetti che condividevano lo stesso spazio degli stati (leggi "valori possibili") e le stesse operazioni, formando così una classe di equivalenza. Se guardi indietro a Smalltalk, dato che puoi aprire una classe e aggiungere metodi, questo è effettivamente lo stesso di quello che puoi fare in Javascript.

Le lingue OO successive volevano essere in grado di utilizzare il controllo statico dei tipi, quindi abbiamo avuto l'idea di un set di classi fisso al momento della compilazione. Nella versione di classe aperta, hai avuto maggiore flessibilità; nella versione più recente, hai avuto la possibilità di verificare alcuni tipi di correttezza nel compilatore che altrimenti avrebbero richiesto test.

In un linguaggio "di classe", tale copia avviene al momento della compilazione. In un linguaggio prototipo, le operazioni sono archiviate nella struttura dati del prototipo, che viene copiata e modificata in fase di esecuzione. In astratto, tuttavia, una classe è ancora la classe di equivalenza di tutti gli oggetti che condividono lo stesso spazio di stato e metodi. Quando aggiungi un metodo al prototipo, stai effettivamente creando un elemento di una nuova classe di equivalenza.

Ora, perché farlo? principalmente perché crea un meccanismo semplice, logico ed elegante in fase di esecuzione. ora, per creare un nuovo oggetto o per creare una nuova classe, è sufficiente eseguire una copia profonda, copiando tutti i dati e la struttura dati del prototipo. Allora ottieni l'ereditarietà e il polimorfismo più o meno gratuitamente: la ricerca del metodo consiste sempre nel chiedere un dizionario per un'implementazione del metodo per nome.

Il motivo che è finito nello script Javascript / ECMA è fondamentalmente che quando abbiamo iniziato con questo 10 anni fa, avevamo a che fare con computer molto meno potenti e browser molto meno sofisticati. Scegliere il metodo basato sul prototipo significava che l'interprete poteva essere molto semplice preservando le proprietà desiderabili dell'orientamento agli oggetti.


1
Giusto, quel paragaph legge come se volessi dire diversamente? Dahl e Nyqvist hanno inventato la "classe" come raccolta di cose con lo stesso metodo di firma.
Charlie Martin,

1
Questo cambiamento lo dice meglio?
Charlie Martin,

2
No, scusa, CLOS è dei sogni di fine anni '80 / CLOS.html Smalltalk del 1980 en.wikipedia.org/wiki/Smalltalk e Simula con orientamento completo degli oggetti dal 1967-68 en.wikipedia.org/wiki/Simula
Charlie Martin,

3
@Stephano, non sono così distinti come tutto ciò: Python, Ruby, Smalltalk usano dizionari per la ricerca dei metodi e javascript e Self hanno classi. In una certa misura, si potrebbe sostenere che la differenza sta proprio nel fatto che i linguaggi orientati al prototipo stanno esponendo le loro implementazioni. Quindi probabilmente è bene non trasformarlo in un grosso problema: probabilmente è più simile all'argomento tra EMACS e vi.
Charlie Martin,

21
Risposta utile . +1 Posta indesiderata meno utile nei commenti. Voglio dire, fa la differenza se CLOS o Smalltalk sono stati i primi? La maggior parte della gente qui non è comunque storica.
Adam Arold,

40

Un confronto, che è leggermente distorto verso l'approccio basato sui prototipi, può essere trovato nell'articolo Self: The Power of Simplicity . Il documento espone i seguenti argomenti a favore dei prototipi:

Creazione mediante copia . La creazione di nuovi oggetti dai prototipi viene eseguita con una semplice operazione, copiando, con una semplice metafora biologica, la clonazione. La creazione di nuovi oggetti dalle classi viene eseguita tramite l'istanza, che include l'interpretazione delle informazioni sul formato in una classe. L'istanza è simile alla costruzione di una casa da un piano. La copia ci attrae come una metafora più semplice dell'istanza.

Esempi di moduli preesistenti . I prototipi sono più concreti delle classi perché sono esempi di oggetti piuttosto che descrizioni di formato e inizializzazione. Questi esempi possono aiutare gli utenti a riutilizzare i moduli rendendoli più facili da comprendere. Un sistema basato su prototipi consente all'utente di esaminare un tipico rappresentante piuttosto che imporre che abbia un senso dalla sua descrizione.

Supporto per oggetti unici nel loro genere . Il sé fornisce un framework che può facilmente includere oggetti unici nel loro genere. Poiché ogni oggetto ha nomi di slot e gli slot possono contenere stato o comportamento, qualsiasi oggetto può avere slot o comportamento univoci. I sistemi basati su classi sono progettati per situazioni in cui vi sono molti oggetti con lo stesso comportamento. Non esiste alcun supporto linguistico per un oggetto che possiede un suo comportamento unico ed è scomodo creare una classe che ha la garanzia di avere solo un'istanza [ pensa al modello singleton ]. Il sé non presenta nessuno di questi svantaggi. Ogni oggetto può essere personalizzato con il suo comportamento. Un oggetto unico può contenere il comportamento univoco e non è necessaria una "istanza" separata.

Eliminazione del meta-regresso . Nessun oggetto in un sistema di classe può essere autosufficiente; è necessario un altro oggetto (la sua classe) per esprimere la sua struttura e comportamento. Questo porta a un meta-regresso concettualmente infinito: a pointè un'istanza di classe Point, che è un'istanza di metaclasse Point, che è un'istanza di metametaclasse Point, all'infinito. D'altra parte, nei sistemi basati su prototipi un oggetto può includere il proprio comportamento; nessun altro oggetto è necessario per respirare la vita in esso. I prototipi eliminano il meta-regresso.

Il Sé è probabilmente il primo linguaggio a implementare i prototipi (ha anche aperto la strada ad altre tecnologie interessanti come la JIT, che in seguito si è fatta strada nella JVM), quindi la lettura delle altre carte del Sé dovrebbe anche essere istruttiva.


5
RE: Eliminazione del meta-regresso: nel Common Lisp Object System, che è basato sulla classe, a pointè un'istanza di classe Point, che è un'istanza di metaclasse standard-class, che è un'istanza di se stessa, ad finitum.
Max Nanasy,

I collegamenti a un self paper sono morti. Collegamenti di lavoro: Sé: il potere della semplicità | A Self Bibliography
user1201917

24

Dovresti dare un'occhiata a un ottimo libro su JavaScript di Douglas Crockford . Fornisce un'ottima spiegazione di alcune delle decisioni di progettazione prese dai creatori di JavaScript.

Uno degli aspetti di progettazione importanti di JavaScript è il suo sistema di ereditarietà prototipo. Gli oggetti sono cittadini di prima classe in JavaScript, tanto che anche le funzioni regolari sono implementate come oggetti (oggetto 'Funzione' per essere precisi). Secondo me, quando inizialmente era progettato per funzionare all'interno di un browser, doveva essere utilizzato per creare molti oggetti singleton. Nel DOM del browser, trovi quella finestra, il documento ecc. Tutti gli oggetti singleton. Inoltre, JavaScript è un linguaggio dinamico tipicamente diffuso (al contrario di dire Python che è fortemente tipizzato, linguaggio dinamico), di conseguenza, un concetto di estensione dell'oggetto è stato implementato attraverso l'uso della proprietà 'prototype'.

Quindi penso che ci siano alcuni pro per OO basati su prototipi implementati in JavaScript:

  1. Adatto in ambienti tipicamente vaghi, non è necessario definire tipi espliciti.
  2. Rende incredibilmente facile implementare il modello singleton (confronta JavaScript e Java a questo proposito e saprai di cosa sto parlando).
  3. Fornisce modi per applicare un metodo di un oggetto nel contesto di un oggetto diverso, aggiungendo e sostituendo i metodi dinamicamente da un oggetto ecc. (Cose che non sono possibili in linguaggi fortemente tipizzati).

Ecco alcuni dei contro del prototipo OO:

  1. Nessun modo semplice per implementare le variabili private. È possibile implementare vari variatori privati ​​usando la magia di Crockford usando le chiusure , ma sicuramente non è banale come usare variabili private in Java o C #.
  2. Non so ancora come implementare eredità multiple (per quello che vale) in JavaScript.

2
Basta usare una convenzione di denominazione per i privati, come fa Python.
aehlke,

1
in js il modo di fare vars privati ​​è con le chiusure, ed è indipendente dal tipo di eredità che scegli.
Benja,

6
Crockford ha fatto molto per danneggiare JavaScript, in quanto un linguaggio di scripting abbastanza semplice è stato trasformato in un fascino da maestro con i suoi interni. JS non ha un vero ambito di parole chiave private o una vera eredità multipla: non tentare di falsarli.
Hal50000,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.