J'apprends à faire de la POO avec JavaScript . At-il le concept d'interface (comme Javainterface
)?
Je pourrais donc créer un auditeur ...
J'apprends à faire de la POO avec JavaScript . At-il le concept d'interface (comme Javainterface
)?
Je pourrais donc créer un auditeur ...
Réponses:
Il n'y a aucune notion de "cette classe doit avoir ces fonctions" (c'est-à-dire, aucune interface en soi), car:
Au lieu de cela, JavaScript utilise ce qu'on appelle la frappe de canard . (S'il marche comme un canard et quacks comme un canard, pour JS, c'est un canard.) Si votre objet a les méthodes quack (), walk () et fly (), le code peut l'utiliser où il le souhaite un objet qui peut marcher, charlatan et voler, sans nécessiter la mise en œuvre d'une interface "Duckable". L'interface est exactement l'ensemble des fonctions que le code utilise (et les valeurs de retour de ces fonctions), et avec la frappe de canard, vous obtenez cela gratuitement.
Maintenant, cela ne veut pas dire que votre code n'échouera pas à mi-parcours, si vous essayez d'appeler some_dog.quack()
; vous obtiendrez une TypeError. Franchement, si vous dites aux chiens de charlatan, vous avez des problèmes légèrement plus importants; la frappe de canard fonctionne mieux lorsque vous gardez tous vos canards dans une rangée, pour ainsi dire, et ne laissez pas les chiens et les canards se mélanger, sauf si vous les traitez comme des animaux génériques. En d'autres termes, même si l'interface est fluide, elle est toujours là; c'est souvent une erreur de passer un chien au code qui s'attend à ce qu'il charlatane et vole en premier lieu.
Mais si vous êtes sûr de faire la bonne chose, vous pouvez contourner le problème des chiens charlatans en testant l'existence d'une méthode particulière avant d'essayer de l'utiliser. Quelque chose comme
if (typeof(someObject.quack) == "function")
{
// This thing can quack
}
Vous pouvez donc vérifier toutes les méthodes que vous pouvez utiliser avant de les utiliser. Cependant, la syntaxe est plutôt moche. Il y a une façon un peu plus jolie:
Object.prototype.can = function(methodName)
{
return ((typeof this[methodName]) == "function");
};
if (someObject.can("quack"))
{
someObject.quack();
}
Il s'agit de JavaScript standard, il devrait donc fonctionner dans n'importe quel interpréteur JS utile. Il a l'avantage supplémentaire de lire comme l'anglais.
Pour les navigateurs modernes (c'est-à-dire à peu près n'importe quel navigateur autre que IE 6-8), il existe même un moyen d'empêcher la propriété d'apparaître dans for...in
:
Object.defineProperty(Object.prototype, 'can', {
enumerable: false,
value: function(method) {
return (typeof this[method] === 'function');
}
}
Le problème est que les objets IE7 n'en ont pas .defineProperty
du tout, et dans IE8, il ne fonctionnerait que sur les objets hôtes (c'est-à-dire les éléments DOM et autres). Si la compatibilité est un problème, vous ne pouvez pas l'utiliser .defineProperty
. (Je ne mentionnerai même pas IE6, car il n'est plus pertinent en dehors de la Chine.)
Un autre problème est que certains styles de codage aiment supposer que tout le monde écrit du mauvais code et interdisent de le modifier Object.prototype
au cas où quelqu'un voudrait l'utiliser aveuglément for...in
. Si vous vous souciez de cela ou utilisez du code ( cassé IMO ), essayez une version légèrement différente:
function can(obj, methodName)
{
return ((typeof obj[methodName]) == "function");
}
if (can(someObject, "quack"))
{
someObject.quack();
}
for...in
est - et a toujours été - confronté à de tels dangers, et quiconque le fait sans au moins considérer que quelqu'un ajouté à Object.prototype
(une technique pas rare, de l'aveu même de cet article) verra son code casser entre les mains de quelqu'un d'autre.
for...in
problème. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
for...in
problème" existera toujours dans une certaine mesure, car il y aura toujours du code bâclé ... enfin, ça, et Object.defineProperty(obj, 'a', {writable: true, enumerable: false, value: 3});
c'est un peu plus de travail que juste obj.a = 3;
. Je peux totalement comprendre les gens qui n'essaient pas de le faire plus souvent. : P
Récupérez une copie de ' modèles de conception JavaScript » de Dustin Diaz . Il y a quelques chapitres dédiés à l'implémentation d'interfaces JavaScript via Duck Typing. C'est aussi une bonne lecture. Mais non, il n'y a pas d'implémentation native d'une interface, vous devez utiliser 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 édition 3) a un implements
mot réservé enregistré pour une utilisation future . Je pense que cela est destiné exactement à cette fin, cependant, pressés de sortir la spécification, ils n'ont pas eu le temps de définir quoi en faire, donc, à l'heure actuelle, les navigateurs ne font rien d'autre laissez-le reposer là-bas et, de temps à autre, vous plaignez si vous essayez de l'utiliser pour quelque chose.
Il est possible et même assez simple de créer le vôtre Object.implement(Interface)
méthode avec une logique qui recule à chaque fois qu'un ensemble particulier de propriétés / fonctions n'est pas implémenté dans un objet donné.
J'ai écrit un article sur l' orientation objet où j'utilise ma propre notation comme suit :
// 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);
Il existe de nombreuses façons d'habiller ce chat en particulier, mais c'est la logique que j'ai utilisée pour ma propre implémentation d'interface. Je trouve que je préfère cette approche, et elle est facile à lire et à utiliser (comme vous pouvez le voir ci-dessus). Cela signifie ajouter une méthode de mise en œuvre à Function.prototype
laquelle certaines personnes peuvent avoir un problème, mais je trouve que cela fonctionne à merveille.
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.'; }}
. Voir le bas du lien de l' article pour un exemple plus élaboré.
Bien que JavaScript ne possède pas leinterface
type, il est souvent nécessaire. Pour des raisons liées à la nature dynamique de JavaScript et à l'utilisation de l'héritage prototypique, il est difficile de garantir des interfaces cohérentes entre les classes - cependant, il est possible de le faire; et fréquemment émulé.
À ce stade, il existe plusieurs manières particulières d'émuler des interfaces en JavaScript; la variance des approches satisfait généralement certains besoins, tandis que d'autres ne sont pas traités. Souvent, l'approche la plus robuste est trop lourde et contrarie le réalisateur (développeur).
Voici une approche des interfaces / classes abstraites qui n'est pas très lourde, est explicative, réduit au minimum les implémentations à l'intérieur des abstractions et laisse suffisamment de place pour les méthodologies dynamiques ou personnalisées:
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
};
Résolveur de préceptes
La resolvePrecept
fonction est une fonction utilitaire et d'assistance à utiliser à l'intérieur de votre classe abstraite . Son travail consiste à permettre une implémentation-gestion personnalisée des préceptes encapsulés (données et comportement) . Il peut générer des erreurs ou avertir - ET - attribuer une valeur par défaut à la classe Implementor.
iAbstractClass
Le iAbstractClass
définit l'interface à utiliser. Son approche implique un accord tacite avec sa classe de mise en œuvre. Cette interface affecte chaque précepte au même espace de noms de précepte exact - OU - à tout ce que renvoie la fonction de résolution de préceptes . Cependant, l'accord tacite se résout en un contexte - une disposition de mise en œuvre.
Réalisateur
Le Implementor simplement « d' accord » avec une interface ( iAbstractClass dans ce cas) et l' applique par l'utilisation de Constructor-Détournement : iAbstractClass.apply(this)
. En définissant les données et le comportement ci-dessus, puis en détournant le constructeur de l'interface - en passant le contexte de l'implémentateur au constructeur de l'interface - nous pouvons nous assurer que les remplacements de l'implémentateur seront ajoutés et que l'interface expliquera les avertissements et les valeurs par défaut.
Il s'agit d'une approche très peu encombrante qui a très bien servi mon équipe et moi au fil du temps et des différents projets. Cependant, il a quelques mises en garde et inconvénients.
Désavantages
Bien que cela aide à implémenter la cohérence dans votre logiciel dans une large mesure, il n'implémente pas de véritables interfaces - mais les émule. Bien que les définitions, les valeurs par défaut et les avertissements ou erreurs soient expliqués, l'explication de l'utilisation est appliquée et affirmée par le développeur (comme avec la plupart des développements JavaScript).
Il s'agit apparemment de la meilleure approche pour les "Interfaces en JavaScript" , mais j'aimerais voir les éléments suivants résolus:
delete
actionsCela dit, j'espère que cela vous aidera autant que mon équipe et moi.
Vous avez besoin d'interfaces en Java car il est typé statiquement et le contrat entre les classes doit être connu lors de la compilation. En JavaScript, c'est différent. JavaScript est typé dynamiquement; cela signifie que lorsque vous obtenez l'objet, vous pouvez simplement vérifier s'il a une méthode spécifique et l'appeler.
yourMethod
à l'entrée n ° 5 de la Superclass
table v, et pour chaque sous-classe qui a la sienne yourMethod
, pointe simplement l'entrée n ° 5 de cette sous-classe à la mise en œuvre appropriée.
Implementation
qui implémente SomeInterface
ne dit pas simplement qu'elle implémente toute l'interface. Il contient des informations indiquant «J'implémente SomeInterface.yourMethod
» et pointe vers la définition de la méthode pour Implementation.yourMethod
. Lorsque la JVM appelle SomeInterface.yourMethod
, elle recherche dans la classe des informations sur les implémentations de la méthode de cette interface et trouve qu'elle doit appeler Implementation.yourMethod
.
J'espère que toute personne qui cherche toujours une réponse la trouvera utile.
Vous pouvez essayer d'utiliser un proxy (c'est standard depuis 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;
}
});
Ensuite, vous pouvez facilement dire:
myMap = {}
myMap.position = latLngLiteral;
Lorsque vous souhaitez utiliser un transcompilateur, vous pouvez essayer TypeScript. Il prend en charge les projets de fonctionnalités ECMA (dans la proposition, les interfaces sont appelées " protocoles ") similaires à ce que font des langages comme coffeescript ou babel.
Dans TypeScript, votre interface peut ressembler à:
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"
}
Ce que vous ne pouvez pas faire:
il n'y a pas d'interfaces natives en JavaScript, il existe plusieurs façons de simuler une interface. j'ai écrit un paquet qui le fait
vous pouvez voir l'implantation ici
Javascript n'a pas d'interfaces. Mais il peut être de type canard, un exemple peut être trouvé ici:
http://reinsbrain.blogspot.com/2008/10/interface-in-javascript.html
Je sais que c'est un ancien, mais je me suis récemment rendu compte que j'avais de plus en plus besoin d'une API pratique pour vérifier les objets par rapport aux interfaces. J'ai donc écrit ceci: https://github.com/tomhicks/methodical
Il est également disponible via NPM: npm install methodical
Il fait essentiellement tout ce qui est suggéré ci-dessus, avec quelques options pour être un peu plus strict, et tout cela sans avoir à faire des charges de passe- if (typeof x.method === 'function')
partout.
J'espère que quelqu'un le trouvera utile.
C'est une vieille question, néanmoins ce sujet ne cesse de me déranger.
Comme bon nombre des réponses ici et sur le Web se concentrent sur «l'application» de l'interface, je voudrais suggérer une autre vue:
Je ressens le manque d'interfaces le plus lorsque j'utilise plusieurs classes qui se comportent de manière similaire (c'est-à-dire implémentent une interface ).
Par exemple, j'ai un générateur de courrier électronique qui s'attend à recevoir des usines de sections de courrier électronique , qui "savent" comment générer le contenu et le code HTML des sections. Par conséquent, ils ont tous besoin d'avoir une sorte de getContent(id)
et getHtml(content)
méthodes.
Le modèle le plus proche des interfaces (bien qu'il s'agisse toujours d'une solution de contournement) auquel je pourrais penser est d'utiliser une classe qui obtiendra 2 arguments, qui définiront les 2 méthodes d'interface.
Le principal défi avec ce modèle est que les méthodes doivent être static
, ou obtenir comme argument l'instance elle-même, pour accéder à ses propriétés. Cependant, il y a des cas où je trouve que ce compromis en vaut la peine.
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));
interface abstraite comme celle-ci
const MyInterface = {
serialize: () => {throw "must implement serialize for MyInterface types"},
print: () => console.log(this.serialize())
}
créer une instance:
function MyType() {
this.serialize = () => "serialized "
}
MyType.prototype = MyInterface
et l'utiliser
let x = new MyType()
x.print()