¿Tiene JavaScript el tipo de interfaz (como la 'interfaz' de Java)?
Estoy aprendiendo a hacer programación orientada a objetos con JavaScript . ¿Tiene el concepto de interfaz (como el de Java interface
)?
Entonces podría crear un oyente...
No existe la noción de "esta clase debe tener estas funciones" (es decir, no hay interfaces per se), porque:
- La herencia de JavaScript se basa en objetos, no en clases. Eso no es gran cosa hasta que te das cuenta:
- JavaScript es un lenguaje de tipado extremadamente dinámico: puedes crear un objeto con los métodos adecuados, lo que lo haría ajustarse a la interfaz, y luego desdefinir todas las cosas que lo hicieron ajustarse . Sería muy fácil subvertir el sistema de tipos... ¡incluso accidentalmente! - que no valdría la pena intentar crear un sistema de tipos en primer lugar.
En cambio, JavaScript utiliza lo que se llama tipificación pato . (Si camina como un pato y grazna como un pato, en lo que a JS le importa, es un pato). Si su objeto tiene métodos quack(), walk() y fly(), el código puede usarlo donde quiera. un objeto que puede caminar, graznar y volar, sin requerir la implementación de alguna interfaz "Duckable". La interfaz es exactamente el conjunto de funciones que utiliza el código (y los valores de retorno de esas funciones), y con la escritura pato, lo obtienes de forma gratuita.
Ahora, eso no quiere decir que su código no fallará a la mitad, si intenta llamar a some_dog.quack()
; obtendrá un TypeError. Francamente, si les estás diciendo a los perros que graznen, tienes problemas un poco mayores; La escritura de patos funciona mejor cuando mantienes todos tus patos en fila, por así decirlo, y no permites que perros y patos se mezclen a menos que los trates como animales genéricos. En otras palabras, aunque la interfaz sea fluida, sigue ahí; A menudo es un error pasar un perro a un código que espera que grazne y vuele en primer lugar.
Pero si está seguro de que está haciendo lo correcto, puede solucionar el problema del graznido del perro probando la existencia de un método particular antes de intentar usarlo. Algo como
if (typeof(someObject.quack) == "function")
{
// This thing can quack
}
Para que pueda verificar todos los métodos que puede usar antes de usarlos. Sin embargo, la sintaxis es un poco fea. Hay una forma un poco más bonita:
Object.prototype.can = function(methodName)
{
return ((typeof this[methodName]) == "function");
};
if (someObject.can("quack"))
{
someObject.quack();
}
Este es JavaScript estándar, por lo que debería funcionar en cualquier intérprete JS que valga la pena usar. Tiene el beneficio adicional de leer como en inglés.
Para los navegadores modernos (es decir, prácticamente cualquier navegador que no sea IE 6-8), incluso existe una manera de evitar que la propiedad aparezca en for...in
:
Object.defineProperty(Object.prototype, 'can', {
enumerable: false,
value: function(method) {
return (typeof this[method] === 'function');
}
}
El problema es que los objetos de IE7 no tienen .defineProperty
nada, y en IE8, supuestamente solo funciona en objetos host (es decir, elementos DOM y demás). Si la compatibilidad es un problema, no puedes usar .defineProperty
. (Ni siquiera mencionaré IE6, porque ya es bastante irrelevante fuera de China).
Otro problema es que a algunos estilos de codificación les gusta asumir que todos escriben código incorrecto y prohíben modificarlo Object.prototype
en caso de que alguien quiera usarlo a ciegas for...in
. Si eso le importa, o está utilizando un código (en mi opinión roto ) que sí lo hace, pruebe con una versión ligeramente diferente:
function can(obj, methodName)
{
return ((typeof obj[methodName]) == "function");
}
if (can(someObject, "quack"))
{
someObject.quack();
}
Obtenga una copia de ' Patrones de diseño de JavaScript ' de Dustin Diaz . Hay algunos capítulos dedicados a implementar interfaces JavaScript a través de Duck Typing. También es una buena lectura. Pero no, no existe una implementación nativa en el idioma de una interfaz, tienes que Duck Type .
// example duck typing method
var hasMethods = function(obj /*, method list as strings */){
var i = 1, methodName;
while((methodName = arguments[i++])){
if(typeof obj[methodName] != 'function') {
return false;
}
}
return true;
}
// in your code
if(hasMethods(obj, 'quak', 'flapWings','waggle')) {
// IT'S A DUCK, do your duck thang
}
JavaScript (ECMAScript edición 3) tiene una implements
palabra reservada guardada para uso futuro . Creo que esto está pensado exactamente para este propósito, sin embargo, en la prisa por publicar la especificación, no tuvieron tiempo de definir qué hacer con ella, por lo que, en este momento, los navegadores no hacen nada más que Déjalo reposar y ocasionalmente quéjate si intentas usarlo para algo.
Es posible y, de hecho, bastante fácil crear su propio Object.implement(Interface)
método con una lógica que se resiste cuando un conjunto particular de propiedades/funciones no se implementa en un objeto determinado.
Escribí un artículo sobre orientación a objetos donde uso mi propia notación de la siguiente manera :
// Create a 'Dog' class that inherits from 'Animal'
// and implements the 'Mammal' interface
var Dog = Object.extend(Animal, {
constructor: function(name) {
Dog.superClass.call(this, name);
},
bark: function() {
alert('woof');
}
}).implement(Mammal);
Hay muchas formas de despellejar a este gato en particular, pero esta es la lógica que utilicé para mi propia implementación de Interfaz. Creo que prefiero este enfoque y es fácil de leer y usar (como puede ver arriba). Significa agregar un método de 'implementación' con Function.prototype
el que algunas personas pueden tener problemas, pero encuentro que funciona muy bien.
Function.prototype.implement = function() {
// Loop through each interface passed in and then check
// that its members are implemented in the context object (this).
for(var i = 0; i < arguments.length; i++) {
// .. Check member's logic ..
}
// Remember to return the class being tested
return this;
}
Interfaces de JavaScript:
Aunque JavaScript no tiene el interface
tipo, muchas veces es necesario. Por razones relacionadas con la naturaleza dinámica de JavaScript y el uso de herencia prototípica, es difícil garantizar interfaces consistentes entre clases; sin embargo, es posible hacerlo; y frecuentemente emulado.
En este punto, existen varias formas particulares de emular interfaces en JavaScript; La variación en los enfoques generalmente satisface algunas necesidades, mientras que otras no se abordan. Muchas veces, el enfoque más sólido es demasiado engorroso y obstaculiza al implementador (desarrollador).
Aquí hay un enfoque para Interfaces/Clases abstractas que no es muy engorroso, es explicativo, mantiene las implementaciones dentro de Abstracciones al mínimo y deja suficiente espacio para metodologías dinámicas o personalizadas:
function resolvePrecept(interfaceName) {
var interfaceName = interfaceName;
return function curry(value) {
/* throw new Error(interfaceName + ' requires an implementation for ...'); */
console.warn('%s requires an implementation for ...', interfaceName);
return value;
};
}
var iAbstractClass = function AbstractClass() {
var defaultTo = resolvePrecept('iAbstractClass');
this.datum1 = this.datum1 || defaultTo(new Number());
this.datum2 = this.datum2 || defaultTo(new String());
this.method1 = this.method1 || defaultTo(new Function('return new Boolean();'));
this.method2 = this.method2 || defaultTo(new Function('return new Object();'));
};
var ConcreteImplementation = function ConcreteImplementation() {
this.datum1 = 1;
this.datum2 = 'str';
this.method1 = function method1() {
return true;
};
this.method2 = function method2() {
return {};
};
//Applies Interface (Implement iAbstractClass Interface)
iAbstractClass.apply(this); // .call / .apply after precept definitions
};
Participantes
Resolución de preceptos
La resolvePrecept
función es una utilidad y una función auxiliar para usar dentro de su clase abstracta . Su trabajo es permitir el manejo personalizado de la implementación de Preceptos encapsulados (datos y comportamiento) . Puede generar errores o advertir, Y, asignar un valor predeterminado a la clase Implementador.
iClaseAbstract
El iAbstractClass
define la interfaz que se utilizará. Su enfoque implica un acuerdo tácito con su clase Implementador. Esta interfaz asigna cada precepto exactamente al mismo espacio de nombres de precepto, O, a lo que devuelva la función Precept Resolver . Sin embargo, el acuerdo tácito se resuelve en un contexto : una disposición del Implementador.
Implementador
El Implementador simplemente 'está de acuerdo' con una Interfaz ( iAbstractClass en este caso) y la aplica mediante el uso de Constructor-Hijacking :. iAbstractClass.apply(this)
Al definir los datos y el comportamiento anteriores, y luego secuestrar el constructor de la Interfaz (pasando el contexto del Implementador al constructor de la Interfaz), podemos asegurar que se agregarán las anulaciones del Implementador y que la Interfaz explicará las advertencias y los valores predeterminados.
Este es un enfoque muy sencillo que nos ha sido muy útil a mi equipo y a mí a lo largo del tiempo y en diferentes proyectos. Sin embargo, tiene algunas advertencias e inconvenientes.
Desventajas
Aunque esto ayuda a implementar coherencia en todo el software en un grado significativo, no implementa interfaces verdaderas , sino que las emula. Aunque se explican las definiciones, los valores predeterminados y las advertencias o errores , el desarrollador aplica y afirma la explicación del uso (como ocurre con gran parte del desarrollo de JavaScript).
Este es aparentemente el mejor enfoque para las "Interfaces en JavaScript" , sin embargo, me encantaría ver resuelto lo siguiente:
- Afirmaciones de tipos de devolución
- Afirmaciones de firmas
- Congelar objetos de
delete
acciones - Afirmaciones de cualquier otra cosa que prevalezca o sea necesaria en la especificidad de la comunidad JavaScript.
Dicho esto, espero que esto te ayude tanto como a mi equipo y a mí.