Sto imparando a fare OOP con JavaScript . Ha il concetto di interfaccia (come Java interface
)?
Quindi sarei in grado di creare un ascoltatore ...
Sto imparando a fare OOP con JavaScript . Ha il concetto di interfaccia (come Java interface
)?
Quindi sarei in grado di creare un ascoltatore ...
Risposte:
Non esiste la nozione di "questa classe deve avere queste funzioni" (cioè nessuna interfaccia in sé), perché:
Invece, JavaScript utilizza ciò che si chiama tipizzazione anatra . (Se cammina come un'anatra e ciondola come un'anatra, per quanto riguarda JS, è un'anatra.) Se il tuo oggetto ha metodi quack (), walk () e fly (), il codice può usarlo dove si aspetta un oggetto che può camminare, ciarlare e volare, senza richiedere l'implementazione di un'interfaccia "Duckable". L'interfaccia è esattamente l'insieme di funzioni che il codice utilizza (e i valori restituiti da quelle funzioni), e con la digitazione duck, la ottieni gratuitamente.
Ora, questo non vuol dire che il tuo codice non fallirà a metà strada, se provi a chiamare some_dog.quack()
; otterrai un TypeError. Francamente, se stai dicendo ai cani di ciarlare, hai problemi leggermente più grandi; la tipizzazione delle anatre funziona meglio quando si tengono tutte le anatre di fila, per così dire, e non si lascia che cani e anatre si mescolino insieme a meno che non si tratti di animali generici. In altre parole, anche se l'interfaccia è fluida, è ancora lì; è spesso un errore passare un cane al codice che si aspetta che cacci e voli in primo luogo.
Ma se sei sicuro di fare la cosa giusta, puoi aggirare il problema del cane ciarlatano testando l'esistenza di un metodo particolare prima di provare a usarlo. Qualcosa di simile a
if (typeof(someObject.quack) == "function")
{
// This thing can quack
}
Quindi puoi verificare tutti i metodi che puoi usare prima di usarli. La sintassi è piuttosto brutta, però. C'è un modo leggermente più carino:
Object.prototype.can = function(methodName)
{
return ((typeof this[methodName]) == "function");
};
if (someObject.can("quack"))
{
someObject.quack();
}
Questo è JavaScript standard, quindi dovrebbe funzionare con qualsiasi interprete JS che valga la pena usare. Ha l'ulteriore vantaggio di leggere come l'inglese.
Per i browser moderni (ovvero praticamente qualsiasi browser diverso da IE 6-8), c'è persino un modo per impedire la visualizzazione della proprietà in for...in
:
Object.defineProperty(Object.prototype, 'can', {
enumerable: false,
value: function(method) {
return (typeof this[method] === 'function');
}
}
Il problema è che gli oggetti IE7 non hanno .defineProperty
affatto, e in IE8, funziona presumibilmente solo su oggetti host (ovvero, elementi DOM e simili). Se la compatibilità è un problema, non è possibile utilizzare .defineProperty
. (Non menzionerò nemmeno IE6, perché è piuttosto irrilevante al di fuori della Cina.)
Un altro problema è che ad alcuni stili di codifica piace supporre che tutti scrivano codice errato e proibiscono di modificarli Object.prototype
nel caso in cui qualcuno voglia utilizzarli ciecamente for...in
. Se ti interessa, o stai usando un codice ( rotto IMO ), prova una versione leggermente diversa:
function can(obj, methodName)
{
return ((typeof obj[methodName]) == "function");
}
if (can(someObject, "quack"))
{
someObject.quack();
}
for...in
è - ed è sempre stato - irto di tali pericoli, e chiunque lo faccia senza almeno considerare che qualcuno aggiunto a Object.prototype
(una tecnica non insolita, per ammissione di quell'articolo) vedrà il proprio codice infrangere nelle mani di qualcun altro.
for...in
problema. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
for...in
problema" esisterà ancora in una certa misura, perché ci sarà sempre un codice sciatto ... beh, quello, ed Object.defineProperty(obj, 'a', {writable: true, enumerable: false, value: 3});
è un po 'più di lavoro che semplice obj.a = 3;
. Posso capire totalmente le persone che non provano a farlo più spesso. : P
Prendi una copia di " Pattern di progettazione JavaScript " di Dustin Diaz . Ci sono alcuni capitoli dedicati all'implementazione delle interfacce JavaScript tramite Duck Typing. È anche una bella lettura. Ma no, non esiste un'implementazione nativa in lingua di un'interfaccia, devi 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 edition 3) ha una implements
parola riservata salvata per uso futuro . Penso che questo sia destinato esattamente a questo scopo, tuttavia, in fretta per ottenere le specifiche fuori dalla porta, non hanno avuto il tempo di definire cosa fare con esso, quindi, al momento, i browser non fanno nulla oltre lascialo stare lì e occasionalmente ti lamenti se provi a usarlo per qualcosa.
È possibile e davvero abbastanza facile crearne uno tuo Object.implement(Interface)
metodo con una logica che sfugge ogni volta che un determinato insieme di proprietà / funzioni non è implementato in un determinato oggetto.
Ho scritto un articolo sull'orientamento agli oggetti dove uso la mia notazione come segue :
// 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);
Esistono molti modi per abbellire questo particolare gatto, ma questa è la logica che ho usato per la mia implementazione dell'interfaccia. Trovo che preferisco questo approccio, ed è facile da leggere e usare (come puoi vedere sopra). Significa aggiungere un metodo "implement" al Function.prototype
quale alcune persone potrebbero avere un problema, ma trovo che funzioni magnificamente.
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.'; }}
. Vedere la parte inferiore del collegamento dell'articolo per un esempio più elaborato.
Sebbene JavaScript non abbiainterface
tipo, spesso è necessario. Per motivi legati alla natura dinamica di JavaScript e all'uso dell'ereditarietà prototipica, è difficile garantire interfacce coerenti tra le classi, tuttavia è possibile farlo; e frequentemente emulato.
A questo punto, esistono alcuni modi particolari per emulare le interfacce in JavaScript; la varianza degli approcci di solito soddisfa alcuni bisogni, mentre altri non vengono affrontati. Spesso l'approccio più solido è eccessivamente ingombrante e ostacola l'implementatore (sviluppatore).
Ecco un approccio alle Interfacce / Classi astratte che non è molto ingombrante, è esplicativo, mantiene al minimo le implementazioni all'interno delle Astrazioni e lascia abbastanza spazio a metodologie dinamiche o personalizzate:
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
};
Risolutore Precetto
La resolvePrecept
funzione è un'utilità e una funzione di supporto da utilizzare all'interno della tua classe astratta . Il suo compito è consentire la gestione personalizzata dell'implementazione dei Precetti incapsulati (dati e comportamento) . Può generare errori o avvisare - AND - assegnare un valore predefinito alla classe Implementor.
iAbstractClass
La iAbstractClass
definisce l'interfaccia da utilizzare. Il suo approccio implica un tacito accordo con la sua classe Implementor. Questa interfaccia assegna ciascun precetto allo stesso spazio dei nomi precetto esatto - O - a qualunque cosa restituisca la funzione Precept Resolver . Tuttavia, l'accordo tacito si risolve in un contesto : una disposizione dell'attore.
implementor
L'implementazioni semplicemente 'd'accordo' con un'interfaccia ( iAbstractClass in questo caso) e lo applica con l'uso di costruttore-Hijacking : iAbstractClass.apply(this)
. Definendo i dati e il comportamento sopra e quindi dirottando il costruttore dell'interfaccia - passando il contesto dell'implementatore al costruttore dell'interfaccia - possiamo garantire che vengano aggiunte le sostituzioni dell'attuatore e che l'interfaccia spiegherà avvisi e valori predefiniti.
Questo è un approccio molto ingombrante che ha servito il mio team e io molto bene nel corso del tempo e in diversi progetti. Tuttavia, presenta alcuni avvertimenti e svantaggi.
svantaggi
Sebbene ciò aiuti a implementare la coerenza in tutto il software in misura significativa, non implementa interfacce vere, ma le emula. Sebbene siano esplicitate definizioni, valori predefiniti e avvertenze o errori , la spiegazione dell'uso viene applicata e affermata dallo sviluppatore (come con gran parte dello sviluppo di JavaScript).
Questo è apparentemente il miglior approccio alle "Interfacce in JavaScript" , tuttavia, mi piacerebbe vedere quanto segue risolto:
delete
azioniDetto questo, spero che questo ti aiuti tanto quanto la mia squadra e io.
Sono necessarie interfacce in Java poiché è tipizzato staticamente e il contratto tra le classi dovrebbe essere noto durante la compilazione. In JavaScript è diverso. JavaScript è digitato in modo dinamico; significa che quando ottieni l'oggetto puoi semplicemente verificare se ha un metodo specifico e chiamarlo.
yourMethod
alla voce # 5 nella Superclass
tabella, e per ogni sottoclasse che ha la sua yourMethod
, indica semplicemente la voce # 5 della sottoclasse all'implementazione appropriata.
Implementation
che implementa SomeInterface
non dice semplicemente che implementa l'intera interfaccia. Ha informazioni che dicono "I implement SomeInterface.yourMethod
" e indica la definizione del metodo per Implementation.yourMethod
. Quando JVM chiama SomeInterface.yourMethod
, cerca nella classe informazioni sulle implementazioni del metodo di quell'interfaccia e trova che deve chiamare Implementation.yourMethod
.
Spero che chiunque stia ancora cercando una risposta la trovi utile.
Puoi provare usando un proxy (è standard da 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;
}
});
Quindi puoi facilmente dire:
myMap = {}
myMap.position = latLngLiteral;
Quando si desidera utilizzare un transcompiler, è possibile provare TypeScript. Supporta bozze di funzionalità ECMA (nella proposta, le interfacce sono chiamate " protocolli ") simili a quelle che fanno linguaggi come coffeescript o babel.
In TypeScript la tua interfaccia può apparire come:
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"
}
Cosa non puoi fare:
non ci sono interfacce native in JavaScript, ci sono diversi modi per simulare un'interfaccia. ho scritto un pacchetto che lo fa
puoi vedere l'impianto qui
Javascript non ha interfacce. Ma può essere dattiloscritto, qui puoi trovare un esempio:
http://reinsbrain.blogspot.com/2008/10/interface-in-javascript.html
So che questo è vecchio, ma recentemente mi sono trovato sempre più bisogno di avere un'API utile per il controllo degli oggetti rispetto alle interfacce. Quindi ho scritto questo: https://github.com/tomhicks/methodical
È disponibile anche tramite NPM: npm install methodical
Praticamente fa tutto ciò che è stato suggerito sopra, con alcune opzioni per essere un po 'più rigoroso, e tutto senza doverne fare un sacco if (typeof x.method === 'function')
boilerplate.
Spero che qualcuno lo trovi utile.
Questa è una vecchia domanda, tuttavia questo argomento non smette mai di infastidirmi.
Poiché molte delle risposte qui e in tutto il web si concentrano sulla "applicazione" dell'interfaccia, vorrei suggerire una visione alternativa:
Sento maggiormente la mancanza di interfacce quando uso più classi che si comportano in modo simile (ovvero implementano un'interfaccia ).
Ad esempio, ho un generatore di e-mail che si aspetta di ricevere le fabbriche di sezioni di e-mail , che "sanno" come generare il contenuto e l'HTML delle sezioni. Quindi, hanno tutti bisogno di avere una sorta di getContent(id)
e getHtml(content)
metodi.
Il modello più vicino alle interfacce (anche se è ancora una soluzione alternativa) che mi viene in mente è l'utilizzo di una classe che otterrà 2 argomenti, che definiranno i 2 metodi dell'interfaccia.
La sfida principale con questo modello è che i metodi devono essere static
, o ottenere come argomento l'istanza stessa, per accedere alle sue proprietà. Tuttavia, ci sono casi in cui trovo che questo compromesso valga 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));
interfaccia astratta come questa
const MyInterface = {
serialize: () => {throw "must implement serialize for MyInterface types"},
print: () => console.log(this.serialize())
}
creare un'istanza:
function MyType() {
this.serialize = () => "serialized "
}
MyType.prototype = MyInterface
e usalo
let x = new MyType()
x.print()