¿Cómo la herencia prototípica es prácticamente diferente de la herencia clásica?

27

La herencia, el polimorfismo y la encapsulación son las tres características más distintas e importantes de la POO, y de ellas, la herencia tiene una alta estadística de uso en estos días. Estoy aprendiendo JavaScript, y aquí, todos dicen que tiene herencia prototípica, y la gente en todas partes dice que es algo muy diferente de la herencia clásica.

Sin embargo, no puedo entender cuál es su diferencia desde el punto de vista práctico. En otras palabras, cuando define una clase base (prototipo) y luego deriva algunas subclases de ella, ambos tienen acceso a funcionalidades de su clase base, y pueden aumentar las funciones en las clases derivadas. Si consideramos que lo que dije es el resultado previsto de la herencia, ¿por qué debería importarnos si estamos usando una versión prototípica o clásica?

Para aclararme más, no veo diferencia en la utilidad y los patrones de uso de la herencia prototípica y clásica. Esto hace que no me interese saber por qué son diferentes, ya que ambos resultan en lo mismo, OOAD. ¿Cuán prácticamente (no teóricamente) la herencia prototípica es diferente de la herencia clásica?

Saeed Neamati
fuente

Respuestas:

6

Publicación de blog reciente sobre JS OO

Creo que su comparación es la emulación OO clásica en JavaScript y la OO clásica y, por supuesto, no puede ver ninguna diferencia.

Descargo de responsabilidad: reemplace todas las referencias a "prototypal OO" con "prototypal OO en JavaScript". No sé los detalles de Self o cualquier otra implementación.

Sin embargo, el prototipo OO es diferente. Con los prototipos solo tiene objetos y solo puede inyectar objetos en la cadena de prototipos de otros objetos. Cada vez que accede a una propiedad en un objeto, busca ese objeto y cualquier objeto en la cadena del prototipo.

Para el prototipo OO no existe la noción de encapsulación. La encapsulación es una característica del alcance, los cierres y las funciones de primera clase, pero no tiene nada que ver con el prototipo OO. Tampoco existe una noción de herencia, lo que la gente llama "herencia" es en realidad solo polimorfismo.

Sin embargo, tiene polimorfismo.

Por ejemplo

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

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

Claramente dtiene acceso al método Dog.walky este exhibe polimorfismo.

Entonces, en realidad hay una gran diferencia . Solo tienes polimorfismo.

Sin embargo, como se mencionó, si desea hacerlo (no tengo idea de por qué lo haría) puede emular OO clásico en JavaScript y tener acceso a la encapsulación y herencia (restringida).

Raynos
fuente
1
@Rayons, Google para la herencia prototípica y verás que surge la herencia prototípica . Incluso de Yahoo!
Saeed Neamati
La herencia de @Saeed es un término vago y comúnmente mal utilizado. Lo que quieren decir con "herencia" lo etiqueto como "polimorfismo". Las definiciones son demasiado vagas.
Raynos
No @Rayons, quise decir que la palabra prototipo es el término correcto, no prototipo . No
hablé de
1
@Saeed, ese es uno de esos errores tipográficos que mi mente simplemente deja en blanco. No le doy mucha atención a eso
Raynos
1
Hmm .. terminología. ick Llamaría a la forma en que los objetos javascript están relacionados con sus prototipos "delegación". El polimorfismo me parece una preocupación de nivel inferior, basado en el hecho de que un nombre dado puede apuntar a un código diferente para diferentes objetos.
Sean McMillan
15

La herencia clásica hereda el comportamiento, sin ningún estado, de la clase padre. Hereda el comportamiento en el momento en que se instancia el objeto.

La herencia de prototipos hereda el comportamiento y el estado del objeto padre. Hereda el comportamiento y el estado en el momento en que se llama al objeto. Cuando el objeto primario cambia en tiempo de ejecución, el estado y el comportamiento de los objetos secundarios se ven afectados.

La "ventaja" de la herencia prototípica es que puede "parchear" el estado y el comportamiento después de que se hayan instanciado todos sus objetos. Por ejemplo, en el marco Ext JS es común cargar "anulaciones" que parchean los componentes principales del marco una vez que se ha instanciado el marco.

Joeri Sebrechts
fuente
44
En la práctica, si estás heredando el estado, te estás preparando para un mundo de dolor. El estado heredado se compartirá si vive en otro objeto.
Sean McMillan
1
Se me ocurre que en JavaScript realmente no hay un concepto de comportamiento separado del estado. Además de la función de constructor, todo comportamiento puede asignarse / modificarse después de la creación del objeto como cualquier otro estado.
Joeri Sebrechts
Entonces, ¿Python no tiene herencia clásica? Si tengo class C(object): def m(self, x): return x*2y luego instance = C()cuando corro instance.m(3)me sale 6. Pero si luego cambio Casí C.m = lambda s, x: x*xy corro instance.m(3)ahora obtengo 9. Lo mismo ocurre si hago un class D(C)y cambio un método en Ccualquier instancia de Drecibir el método cambiado también. ¿Estoy malinterpretando o esto significa que Python no tiene herencia clásica según su definición?
mVChr
@mVChr: ​​Todavía está heredando el comportamiento y no el estado en ese caso, lo que apunta a la herencia clásica, pero señala un problema con mi definición de "hereda el comportamiento en el momento en que se instancia el objeto". No estoy muy seguro de cómo corregir la definición.
Joeri Sebrechts
9

Primero: la mayoría de las veces, usarás objetos, no los definirás, y usar objetos es lo mismo bajo ambos paradigmas.

Segundo: la mayoría de los entornos prototípicos utilizan el mismo tipo de división que los entornos basados ​​en clases: datos mutables en la instancia, con métodos heredados. Entonces hay muy poca diferencia de nuevo. (Véase mi respuesta a esta pregunta desbordamiento de pila , y el mismo papel la organización de programas sin clases . Mira Citeseer para una versión PDF .)

Tercero: la naturaleza dinámica de Javascript tiene una influencia mucho mayor que el tipo de herencia. El hecho de que pueda agregar un nuevo método a todas las instancias de un tipo asignándolo al objeto base es claro, pero puedo hacer lo mismo en Ruby, reabriendo la clase.

Cuarto: las diferencias prácticas son pequeñas, mientras que la cuestión práctica de olvidarse del uso newes mucho mayor, es decir, es mucho más probable que se vea afectado por la falta de un newcódigo que por la diferencia entre el código prototípico y el clásico. .

Dicho todo esto, la diferencia práctica entre la herencia prototípica y la clásica es que sus métodos (clases) de cosas que mantienen son los mismos que sus datos (instancias) de cosas que mantienen. Esto significa que puede desarrollar sus clases poco a poco, usando las mismas herramientas de manipulación de objetos que usarías en cualquier instancia. (De hecho, así es como lo hacen todas las bibliotecas de emulación de clase. Para un enfoque que no sea como todos los demás, mira Traits.js ). Esto es principalmente interesante si está realizando una metaprogramación.

Sean McMillan
fuente
yay, mejor respuesta IMO!
Aivar
0

La herencia de prototipos en JavaScript es diferente de las clases en estas formas importantes:

Los constructores son simplemente funciones que puede llamar sin 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.

No hay variables o métodos privados, en el mejor de los casos puede hacer esto:

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!
}

En el ejemplo anterior, no puede extender la clase de manera significativa si se recurre a variables y métodos privados falsos y, además, cualquier método público que haya declarado se volverá a crear cada vez que se cree una nueva instancia.

Bjorn
fuente
Puede ampliar la "clase" de manera significativa. Simplemente no se puede acceder a las variables locales color, r, x, yy drawCircleque están vinculados con el ámbito del léxicoCircle
Raynos
Es cierto, puedes, pero no puedes hacerlo sin crear un montón de métodos 'públicos' agregados al contexto que se crean cada vez que creas una instancia.
Bjorn
Este es un patrón muy extraño que estás usando allí. Circle.call()como constructor? Parece que estás tratando de describir "objetos funcionales" (un nombre inapropiado ...)
Sean McMillan
No es algo que haría, solo digo que es posible llamar al constructor una y otra vez con JavaScript, pero no en idiomas con clases tradicionales.
Bjorn
1
Pero, ¿cómo son 'importantes' estas diferencias? No veo construir objetos llamando a una función, no usando 'nuevo', como 'importante', solo una pequeña diferencia de sintaxis. Del mismo modo, no tener variables privadas no es fundamentalmente diferente, solo una pequeña diferencia. Además, puede tener (algo similar a) variables privadas, en la forma en que lo describe.
Roel