Estoy aprendiendo a hacer POO con JavaScript . ¿Tiene el concepto de interfaz (como el de Java interface
)?
Entonces podría crear un oyente ...
Estoy aprendiendo a hacer POO con JavaScript . ¿Tiene el concepto de interfaz (como el de Java interface
)?
Entonces podría crear un oyente ...
Respuestas:
No existe la noción de "esta clase debe tener estas funciones" (es decir, no hay interfaces per se), porque:
En cambio, JavaScript usa lo que se llama escribir pato . (Si camina como un pato y grazna como un pato, por 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 lo espera 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 usa el código (y los valores de retorno de esas funciones), y al escribir pato, obtienes eso de forma gratuita.
Ahora, eso no quiere decir que su código no fallará a la mitad, si intenta llamar some_dog.quack()
; obtendrás un TypeError. Francamente, si le estás diciendo a los perros que graznen, tienes problemas un poco mayores; la escritura de patos funciona mejor cuando mantiene a todos sus patos en fila, por así decirlo, y no permite que los perros y los patos se mezclen a menos que los trate como animales genéricos. En otras palabras, aunque la interfaz es fluida, sigue ahí; a menudo es un error pasar un perro al 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 perro graznando probando la existencia de un método en particular antes de intentar usarlo. Algo como
if (typeof(someObject.quack) == "function")
{
// This thing can quack
}
Por lo tanto, puede 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 el inglés.
Para los navegadores modernos (es decir, prácticamente cualquier navegador que no sea IE 6-8), incluso hay 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 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 puede 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 suponer que todos escriben códigos incorrectos y prohíben la modificación Object.prototype
en caso de que alguien quiera usar a ciegas for...in
. Si le importa eso o está usando un código (IMO roto ) que lo hace, intente con una versión ligeramente diferente:
function can(obj, methodName)
{
return ((typeof obj[methodName]) == "function");
}
if (can(someObject, "quack"))
{
someObject.quack();
}
for...in
está, y siempre ha estado, cargado de tales peligros, y cualquiera que lo haga sin al menos considerar que alguien agregado Object.prototype
(una técnica no poco común, según la propia admisión de ese artículo) verá su código descifrado en las manos de otra persona.
for...in
problema. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
for...in
problema" seguirá existiendo hasta cierto punto, porque siempre habrá un código descuidado ... bueno, eso, y Object.defineProperty(obj, 'a', {writable: true, enumerable: false, value: 3});
es bastante más trabajo que simplemente obj.a = 3;
. Puedo entender totalmente a las personas que no intentan hacerlo con más frecuencia. : P
Recoge 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. Es una buena lectura también. Pero no, no hay una implementación nativa del 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á destinado exactamente para este propósito, sin embargo, en un apuro por sacar la especificación por la puerta, no tuvieron tiempo de definir qué hacer con ella, por lo que, en este momento, los navegadores no hacen nada más déjalo reposar allí y ocasionalmente queja 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 retuerce cuando un conjunto particular de propiedades / funciones no se implementa en un objeto dado.
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 crear 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 el Function.prototype
que algunas personas pueden tener un problema, pero creo que funciona de maravilla.
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;
}
var interf = arguments[i]; for (prop in interf) { if (this.prototype[prop] === undefined) { throw 'Member [' + prop + '] missing from class definition.'; }}
. Consulte la parte inferior del enlace del artículo para obtener un ejemplo más elaborado.
Aunque JavaScript no tiene el interface
tipo, a menudo es necesario. Por razones relacionadas con la naturaleza dinámica de JavaScript y el uso de la herencia prototípica, es difícil garantizar interfaces consistentes entre las clases; sin embargo, es posible hacerlo; y frecuentemente emulado.
En este punto, hay 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 robusto es demasiado engorroso y obstaculiza el implementador (desarrollador).
Aquí hay un enfoque de 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
};
Resolver preceptos
La resolvePrecept
función es una función de utilidad y ayuda 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 arrojar errores o advertir - Y - asignar un valor predeterminado a la clase Implementador.
iAbstractClass
El iAbstractClass
define la interfaz que se utilizará. Su enfoque implica un acuerdo tácito con su clase Implementor. Esta interfaz asigna cada precepto al mismo espacio de nombres de precepto exacto, O, a lo que devuelva la función de resolución de preceptos . 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 lo aplica por el uso de Constructor-Secuestro : 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 asegurarnos de que se agreguen las anulaciones del Implementador, y que la Interfaz explicará las advertencias y los valores predeterminados.
Este es un enfoque no engorroso que nos ha servido a mi equipo y a mí muy bien durante el transcurso del tiempo y diferentes proyectos. Sin embargo, tiene algunas advertencias y desventajas.
Inconvenientes
Aunque esto ayuda a implementar consistencia en todo su software en un grado significativo, no implementa interfaces verdaderas , sino que las emula. Aunque las definiciones, valores predeterminados, y las advertencias o errores son explicadas, la explicación de su uso se hace cumplir y afirmó por el desarrollador (como en gran parte del desarrollo de JavaScript).
Este es aparentemente el mejor enfoque para "Interfaces en JavaScript" , sin embargo, me encantaría ver lo siguiente resuelto:
delete
accionesDicho esto, espero que esto te ayude tanto como a mi equipo y a mí.
Necesita interfaces en Java, ya que está estáticamente tipado y el contrato entre clases debe conocerse durante la compilación. En JavaScript es diferente. JavaScript se escribe dinámicamente; significa que cuando obtienes el objeto puedes verificar si tiene un método específico y llamarlo.
yourMethod
en la entrada # 5 en la Superclass
tabla v, y para cada subclase que tiene la suya yourMethod
, simplemente señala la entrada de la subclase # 5 en la implementación apropiada.
Implementation
que implementa SomeInterface
no solo dice que implementa toda la interfaz. Tiene información que dice "Implemento SomeInterface.yourMethod
" y apunta a la definición del método para Implementation.yourMethod
. Cuando la JVM llama SomeInterface.yourMethod
, busca en la clase información sobre implementaciones del método de esa interfaz, y encuentra que necesita llamar Implementation.yourMethod
.
Espero que cualquier persona que todavía esté buscando una respuesta la encuentre útil.
Puede probar usando un Proxy (es estándar desde ECMAScript 2015): https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy
latLngLiteral = new Proxy({},{
set: function(obj, prop, val) {
//only these two properties can be set
if(['lng','lat'].indexOf(prop) == -1) {
throw new ReferenceError('Key must be "lat" or "lng"!');
}
//the dec format only accepts numbers
if(typeof val !== 'number') {
throw new TypeError('Value must be numeric');
}
//latitude is in range between 0 and 90
if(prop == 'lat' && !(0 < val && val < 90)) {
throw new RangeError('Position is out of range!');
}
//longitude is in range between 0 and 180
else if(prop == 'lng' && !(0 < val && val < 180)) {
throw new RangeError('Position is out of range!');
}
obj[prop] = val;
return true;
}
});
Entonces puedes decir fácilmente:
myMap = {}
myMap.position = latLngLiteral;
Cuando desee utilizar un transcompilador, puede probar TypeScript. Admite características de borrador de ECMA (en la propuesta, las interfaces se denominan " protocolos ") similares a lo que hacen los lenguajes como coffeescript o babel.
En TypeScript, su interfaz puede verse así:
interface IMyInterface {
id: number; // TypeScript types are lowercase
name: string;
callback: (key: string; value: any; array: string[]) => void;
type: "test" | "notATest"; // so called "union type"
}
Lo que no puedes hacer:
no hay interfaces nativas en JavaScript, hay varias formas de simular una interfaz. He escrito un paquete que lo hace
puedes ver la implantación aquí
Javascript no tiene interfaces. Pero puede ser tipado, un ejemplo se puede encontrar aquí:
http://reinsbrain.blogspot.com/2008/10/interface-in-javascript.html
Sé que este es antiguo, pero recientemente me he encontrado necesitando cada vez más tener una API práctica para verificar objetos en las interfaces. Entonces escribí esto: https://github.com/tomhicks/methodical
También está disponible a través de NPM: npm install methodical
Básicamente, hace todo lo sugerido anteriormente, con algunas opciones para ser un poco más estricto, y todo sin tener que hacer un montón de if (typeof x.method === 'function')
repeticiones.
Esperemos que alguien lo encuentre útil.
Esta es una vieja pregunta, sin embargo, este tema nunca deja de molestarme.
Como muchas de las respuestas aquí y en toda la web se centran en "hacer cumplir" la interfaz, me gustaría sugerir una vista alternativa:
Siento la falta de interfaces más cuando uso múltiples clases que se comportan de manera similar (es decir, implementan una interfaz ).
Por ejemplo, tengo un Generador de correo electrónico que espera recibir Fábricas de secciones de correo electrónico , que "saben" cómo generar el contenido y HTML de las secciones. Por lo tanto, todos tenemos que tener algún tipo de getContent(id)
y getHtml(content)
métodos.
El patrón más cercano a las interfaces (aunque todavía es una solución alternativa) podría pensar en usar una clase que obtenga 2 argumentos, que definirán los 2 métodos de interfaz.
El principal desafío con este patrón es que los métodos tienen que ser static
, o para obtener como argumento la instancia misma, para poder acceder a sus propiedades. Sin embargo, hay casos en los que creo que esta compensación vale la pena.
class Filterable {
constructor(data, { filter, toString }) {
this.data = data;
this.filter = filter;
this.toString = toString;
// You can also enforce here an Iterable interface, for example,
// which feels much more natural than having an external check
}
}
const evenNumbersList = new Filterable(
[1, 2, 3, 4, 5, 6], {
filter: (lst) => {
const evenElements = lst.data.filter(x => x % 2 === 0);
lst.data = evenElements;
},
toString: lst => `< ${lst.data.toString()} >`,
}
);
console.log('The whole list: ', evenNumbersList.toString(evenNumbersList));
evenNumbersList.filter(evenNumbersList);
console.log('The filtered list: ', evenNumbersList.toString(evenNumbersList));
interfaz abstracta como esta
const MyInterface = {
serialize: () => {throw "must implement serialize for MyInterface types"},
print: () => console.log(this.serialize())
}
crear una instancia:
function MyType() {
this.serialize = () => "serialized "
}
MyType.prototype = MyInterface
y úsalo
let x = new MyType()
x.print()