JavaScript a-t-il le type d'interface (comme «l'interface» de Java)?

Réponses:

650

Il n'y a aucune notion de "cette classe doit avoir ces fonctions" (c'est-à-dire, aucune interface en soi), car:

  1. L'héritage JavaScript est basé sur des objets, pas sur des classes. Ce n'est pas un gros problème jusqu'à ce que vous réalisiez:
  2. JavaScript est un langage typé de manière extrêmement dynamique - vous pouvez créer un objet avec les méthodes appropriées, ce qui le rendrait conforme à l'interface, puis indéfinir tout ce qui l'a rendu conforme . Il serait si facile de renverser le système de saisie - même accidentellement! - qu'il ne vaudrait pas la peine d'essayer de créer un système de type en premier lieu.

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 .definePropertydu 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.prototypeau 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();
}
cHao
la source
7
Ce n'est pas aussi horrible qu'on le prétend. for...inest - 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.
cHao
1
@entonio: Je considérerais la malléabilité des types intégrés comme une caractéristique plutôt qu'un problème. C'est une grande partie de ce qui rend les cales / polyfills réalisables. Sans cela, nous emballerions tous les types intégrés avec des sous-types éventuellement incompatibles ou attendrions la prise en charge universelle du navigateur (ce qui pourrait ne jamais arriver, lorsque les navigateurs ne prennent pas en charge des éléments parce que les gens ne l'utilisent pas car les navigateurs ne le font pas '' t le soutenir). Étant donné que les types intégrés peuvent être modifiés, nous pouvons simplement ajouter de nombreuses fonctions qui n'existent pas encore.
cHao
1
Dans la dernière version de Javascript (1.8.5), vous pouvez définir la propriété d'un objet comme non énumérable. De cette façon, vous pouvez éviter le for...inproblème. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Tomas Prado
1
@ Tomás: Malheureusement, jusqu'à ce que chaque navigateur exécute quelque chose de compatible avec ES5, nous devons toujours nous préoccuper de ce genre de choses. Et même dans ce cas, le " for...inproblè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
cHao
1
Hehe ... j'adore le "Franchement, si vous dites aux chiens de charlatan, vous avez des problèmes un peu plus gros. Grande analogie pour montrer que les langues ne devraient pas essayer d'éviter la stupidité. C'est toujours une bataille perdue. -Scott
Skooppa. com
73

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
}
BGerrissen
la source
La méthode décrite dans le livre "pro javascript design patterns" est probablement la meilleure approche d'un tas de choses que j'ai lu ici et de ce que j'ai essayé. Vous pouvez utiliser l'héritage par-dessus, ce qui rend encore mieux le suivi des concepts de POO. Certains pourraient prétendre que vous n'avez pas besoin de concepts POO dans JS, mais je vous prie de différer.
animageofmine
21

JavaScript (ECMAScript édition 3) a un implementsmot 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.prototypelaquelle 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;
}
Steven de Salas
la source
4
Cette syntaxe me fait vraiment mal au cerveau, mais l'implémentation ici est assez intéressante.
Cypher
2
Javascript est obligé de faire cela (blesser le cerveau) spécialement lorsqu'il provient d'implémentations de langage OO plus propres.
Steven de Salas
10
@StevendeSalas: Eh. JS a tendance à être assez propre lorsque vous arrêtez de le traiter comme un langage orienté classe. Toutes les conneries nécessaires pour émuler des classes, des interfaces, etc ... c'est ce qui va vraiment faire mal à votre cerveau. Des prototypes? Des trucs simples, vraiment, une fois que vous cessez de les combattre.
cHao
ce qui est dans "// .. Vérifiez la logique du membre." ? A quoi cela ressemble-t-il?
PositiveGuy
Salut @We, vérifier la logique des membres signifie parcourir les propriétés souhaitées et lancer une erreur s'il en manque une 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é.
Steven de Salas
12

Interfaces JavaScript:

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

Les participants

Résolveur de préceptes

La resolvePreceptfonction 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 iAbstractClassdé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:

  • Assertions de types de retour
  • Assertions de signatures
  • Geler les objets des deleteactions
  • Affirmations de tout autre élément répandu ou nécessaire dans la spécificité de la communauté JavaScript

Cela dit, j'espère que cela vous aidera autant que mon équipe et moi.

Cody
la source
7

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.

Alex Reitbort
la source
1
En fait, vous n'avez pas besoin d'interfaces en Java, il est sûr de s'assurer que les objets ont une certaine API afin que vous puissiez les échanger contre d'autres implémentations.
BGerrissen
3
Non, ils sont en fait nécessaires en Java pour qu'il puisse créer des vtables pour les classes qui implémentent une interface au moment de la compilation. Déclarer qu'une classe implémente une interface demande au compilateur de construire une petite structure qui contient des pointeurs vers toutes les méthodes nécessaires à cette interface. Sinon, il devrait être distribué par nom lors de l'exécution (comme le font les langages à typage dynamique).
munificent
Je ne pense pas que ce soit correct. La répartition est toujours dynamique en java (sauf si peut-être qu'une méthode est définitive), et le fait que la méthode appartient à une interface ne change pas les règles de recherche. La raison pour laquelle les interfaces sont nécessaires dans les langages de type statique est que vous pouvez utiliser le même «pseudo-type» (l'interface) pour faire référence à des classes non liées.
entonio
2
@entonio: Dispatch n'est pas aussi dynamique qu'il y paraît. La méthode actuelle n'est souvent pas connue jusqu'à l'exécution, grâce au polymorphisme, mais le bytecode ne dit pas "invoke yourMethod"; il dit "invoquer Superclass.yourMethod". La machine virtuelle Java ne peut pas invoquer une méthode sans savoir dans quelle classe la rechercher. Lors de la liaison, elle peut mettre yourMethodà l'entrée n ° 5 de la Superclasstable 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.
cHao
1
@entonio: Pour les interfaces, les règles ne changent un peu. (Pas dans le langage, mais le bytecode généré et le processus de recherche de la JVM sont différents.) Une classe nommée Implementationqui implémente SomeInterfacene 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.
cHao
6

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;
shaedrich
la source
5

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:

shaedrich
la source
3

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

Amit Wagner
la source
2

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

Reinsbrain
la source
J'aime le modèle que l'article de ce lien utilise pour faire des affirmations sur le type. Une erreur générée lorsque quelque chose n'implémente pas la méthode supposée est exactement ce à quoi je m'attendais, et j'aime la façon dont je peux regrouper ces méthodes requises (comme une interface) si je le fais de cette façon.
Eric Dubé
1
Je déteste le transpiling (et les cartes sources pour le débogage) mais Typescript est si proche de ES6 que je suis enclin à garder le nez et à plonger dans Typescript. ES6 / Typescript est intéressant car il vous permet d'inclure des propriétés en plus des méthodes lors de la définition d'une interface (comportement).
Reinsbrain
1

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.

À M
la source
Tom, je viens de regarder une vidéo AngularJS TDD et lorsqu'il installe un framework, l'un des packages dépendants est votre package méthodique! Bon travail!
Cody
Haha excellent. Je l'ai essentiellement abandonné après que les gens au travail m'ont convaincu que les interfaces en JavaScript étaient interdites. Récemment, j'ai eu une idée d'une bibliothèque qui procède essentiellement à un proxy pour s'assurer que seules certaines méthodes y sont utilisées, ce qui est essentiellement ce qu'est une interface. Je pense toujours que les interfaces ont une place en JavaScript! Pouvez-vous lier cette vidéo au fait? Je voudrais jeter un oeil.
Tom
Tu paries, Tom. Je vais essayer de le trouver bientôt. Merci aussi l'anecdote sur les interfaces en tant que proxys. À votre santé!
Cody
1

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));

GalAbra
la source
0

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()
Kevin Krausse
la source