web-dev-qa-db-fra.com

Est-ce que JavaScript a le type d'interface (tel que "l'interface" de Java)?

J'apprends comment faire OOP avec JavaScript . At-il le concept d'interface (tel que interface de Java)?

Donc, je serais capable de créer un auditeur ...

297
Tom Brito

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

  1. L'héritage JavaScript est basé sur des objets, pas des classes. Ce n'est pas grave jusqu'à ce que vous réalisiez:
  2. JavaScript est un langage extrêmement typé - vous pouvez créer un objet avec les méthodes appropriées, ce qui le rendrait conforme à l’interface et ensuite définir toutes les choses qui l’ont rendu conforme . Il serait si facile de renverser le système de typage - même accidentellement! - qu'il ne vaudrait pas la peine d'essayer de créer un système de types en premier lieu.

Au lieu de cela, JavaScript utilise ce qui s'appelle dactylographie . (S'il marche comme un canard et que les charlatans ressemblent à un canard, autant que JS le soucie, 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, charmer 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 saisie simplifiée, vous l'obtenez gratuitement.

Cela ne veut pas dire que votre code n'échouera pas à la moitié si vous essayez d'appeler some_dog.quack(); vous obtiendrez un TypeError. Franchement, si vous dites aux chiens de charrier, vous avez des problèmes un peu plus gros; La dactylographie fonctionne mieux lorsque vous maintenez tous vos canards dans une rangée, pour ainsi dire, et que vous ne laissez pas les chiens et les canards se mélanger à moins que vous ne les traitiez 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 à un code qui s'attend à ce qu'il charrie et vole en premier.

Mais si vous êtes certain de faire le bon choix, vous pouvez contourner le problème des 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
}

Ainsi, vous pouvez vérifier toutes les méthodes que vous pouvez utiliser avant de les utiliser. La syntaxe est un peu moche, cependant. Il y a une manière un peu plus jolie:

Object.prototype.can = function(methodName)
{
     return ((typeof this[methodName]) == "function");
};

if (someObject.can("quack"))
{
    someObject.quack();
}

Ceci est du code JavaScript standard, il devrait donc fonctionner dans tout interpréteur JS intéressant. Il présente l'avantage supplémentaire de lire comme l'anglais.

Pour les navigateurs modernes (c'est-à-dire pratiquement n'importe quel navigateur autre que IE 6-8), il existe même un moyen d'empêcher l'affichage de la propriété 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'ont pas du tout .defineProperty et que dans IE8, cela ne fonctionnerait que sur les objets Host (c'est-à-dire les éléments DOM et autres). Si la compatibilité pose problème, vous ne pouvez pas utiliser .defineProperty. (Je ne mentionnerai même pas IE6, car ce n'est plus vraiment pertinent en dehors de la Chine.)

Un autre problème est que certains styles de codage aiment supposer que tout le monde écrit un code incorrect et interdisent de modifier Object.prototype au cas où quelqu'un voudrait utiliser aveuglément for...in. Si cela vous intéresse ou si vous utilisez un code (IMO cassé ), essayez une version légèrement différente:

function can(obj, methodName)
{
     return ((typeof obj[methodName]) == "function");
}

if (can(someObject, "quack"))
{
    someObject.quack();
}
584
cHao

Procurez-vous une copie de ' patterns de conception JavaScript ' par Dustin Diaz . Quelques chapitres sont consacrés à la mise en oeuvre d'interfaces JavaScript via Duck Typing. C'est une bonne lecture aussi. Mais non, il n'y a pas d'implémentation native en langage d'une interface, vous devez Type de canard .

// 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
}
66
BGerrissen

JavaScript (édition ECMAScript 3) a un implements mot réservé sauvegardé pour une utilisation future . Je pense que cela est exactement prévu à cet effet, cependant, dans le souci pressant de faire passer le cahier des charges, ils n’ont pas eu le temps de définir quoi en faire, de sorte que, pour le moment, les navigateurs ne font rien d'autre que laissez-le reposer là-bas et de temps en temps vous plaindre si vous essayez de l'utiliser pour quelque chose.

Il est possible et assez simple de créer votre propre méthode Object.implement(Interface) avec une logique qui bloque 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 orientation objetoù utiliser 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 y a beaucoup de façons de skinner 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 veut dire ajouter une méthode 'implémenter' à Function.prototype avec 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;
}
19
Steven de Salas

Interfaces JavaScript:

Bien que JavaScript n'ait pas le type interface, il est souvent nécessaire. Pour des raisons liées à la nature dynamique de JavaScript et à l'utilisation de l'héritage prototypical, il est difficile de garantir la cohérence des interfaces entre les classes. Toutefois, il est possible de le faire. et fréquemment imité.

À ce stade, il existe une poignée de méthodes particulières pour émuler les interfaces en JavaScript; la variance des approches répond généralement à certains besoins, tandis que d’autres ne sont pas traitées. Souvent, l'approche la plus robuste est trop lourde et bloque l'implémenteur (développeur).

Voici une approche des classes d'interfaces/abstraites peu lourde, explicative, minimisant les implémentations dans les Abstractions et laissant suffisamment de place aux 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
};

Participants

résolveur de préceptes

La fonction resolvePrecept est un utilitaire et une fonction d'assistance à utiliser dans votre classe abstraite . Son travail consiste à permettre la gestion personnalisée de la mise en œuvre des préceptes (données et comportements) encapsulés . Il peut renvoyer des erreurs ou avertir - ET - d'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 assigne chaque précepte au même espace de nom de précepte exact - OR - à quelque soit le La fonction de précepteur résolveur est renvoyée. Cependant, l'accord tacite résout un contexte - une disposition de Implementor.

Exécuteur

L’implémenteur "accepte" simplement une interface ( iAbstractClass dans ce cas) et l’applique à l’aide de Constructor - Détournement : iAbstractClass.apply(this). En définissant les données et le comportement ci-dessus, puis piratant le constructeur de l'interface - en transmettant le contexte de l'implémenteur au constructeur de l'interface - nous pouvons nous assurer que les remplacements de l'implémenteur être ajouté et cette interface expliquera les avertissements et les valeurs par défaut.

C'est une approche très peu lourde qui a très bien servi mon équipe et moi-même au fil du temps et des différents projets. Cependant, il y a quelques réserves et inconvénients.

Inconvénients

Bien que cela aide à mettre en œuvre la cohérence de votre logiciel dans une mesure significative, ne met pas en oeuvre 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 d'utilisation est appliquée et affirmée par le développeur (comme beaucoup de développement JavaScript).

C'est apparemment la meilleure approche de "Interfaces en JavaScript" , cependant, j'aimerais beaucoup que ce qui suit soit résolu:

  • Assertions des types de retour
  • Assertions de signatures
  • Gel des objets des actions delete
  • Affirmations de tout ce qui prévaut ou qui est nécessaire dans la spécificité de la communauté JavaScript

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

12
Cody

J'espère que tous ceux qui recherchent encore une réponse trouvent cela utile.

Vous pouvez essayer d’utiliser un proxy (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;
5
shaedrich

Vous avez besoin d’interfaces dans Java car il est typé de manière statique 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.

4
Alex Reitbort

Lorsque vous souhaitez utiliser un transcompilateur, vous pouvez essayer TypeScript. Il prend en charge les projets de fonctionnalités ECMA similaires aux langages tels que 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 tu ne peux pas faire:

  • Définir des modèles RegExp pour la valeur de type
  • Définir la validation comme longueur de chaîne
  • Nombre de plages
  • etc.
2
shaedrich

Javascript n'a pas d'interfaces. Mais il peut être typé, un exemple peut être trouvé ici:

http://reinsbrain.blogspot.com/2008/10/interface-in-javascript.html

2
Reinsbrain

il n'y a pas d'interface native 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

1
Amit Wagner

Je sais que c’est un ancien système, mais j’ai récemment constaté qu’il me fallait de plus en plus disposer 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 if (typeof x.method === 'function').

Espérons que quelqu'un le trouve utile.

1
Tom