381 votes

JavaScript dispose-t-il d'un type d'interface (comme l'"interface" de Java) ?

J'apprends comment faire de la POO avec JavaScript . Possède-t-il le concept d'interface (tel que celui de Java interface ) ?

Donc, je pourrais créer un écouteur...

24 votes

Pour ceux qui cherchent d'autres options, TypeScript a interfaces .

2 votes

Une autre option si vous voulez utiliser la JS vanille est implement.js comme démontré aquí

746voto

cHao Points 42294

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

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

Au lieu de cela, JavaScript utilise ce que l'on appelle dactylographie du canard . (S'il marche comme un canard, et fait coin-coin comme un canard, pour autant que JS s'en soucie, c'est un canard). Si votre objet possède des méthodes quack(), walk() et fly(), le code peut l'utiliser partout où il attend un objet qui peut marcher, quack et voler, sans avoir à implémenter 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 le typage canard, vous obtenez cela gratuitement.

Maintenant, cela ne veut pas dire que votre code ne va pas échouer à mi-chemin, si vous essayez d'appeler some_dog.quack() vous obtiendrez une TypeError. Franchement, si vous demandez à des chiens de faire coin-coin, vous avez des problèmes un peu plus importants ; la typographie des canards fonctionne mieux lorsque vous gardez 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 fasse coin-coin et vole en premier lieu.

Mais si vous êtes sûr de faire ce qu'il faut, vous pouvez contourner le problème du chien de faïence 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 cependant assez moche. Il existe un moyen un peu plus joli :

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

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

Il s'agit d'un JavaScript standard, qui devrait donc fonctionner dans tout interpréteur JS digne de ce nom. Il a l'avantage supplémentaire de se lire comme de l'anglais.

Pour les navigateurs modernes (c'est-à-dire, à peu près tous les navigateurs autres que IE 6-8), il existe même un moyen d'empêcher la propriété d'apparaître dans le champ for...in :

Object.defineProperty(Object.prototype, 'can', {
    enumerable: false,
    value: function(method) {
        return (typeof this[method] === 'function');
    }
}

Le problème est que les objets d'IE7 ne disposent pas de .defineProperty et dans IE8, il ne fonctionne prétendument 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 utiliser la fonction .defineProperty . (Je ne mentionnerai même pas IE6, car il n'est plus guère pertinent en dehors de la Chine).

Un autre problème est que certains styles de codage partent du principe que tout le monde écrit du mauvais code, et interdisent de modifier Object.prototype au cas où quelqu'un voudrait utiliser aveuglément for...in . Si vous vous souciez de cela, ou si vous utilisez (IMO brisé ) code qui le fait, essayez une version légèrement différente :

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

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

7 votes

Ce n'est pas aussi horrible que ça en a l'air. for...in est -- et a toujours été -- rempli de tels dangers, et quiconque le fait sans au moins considérer que quelqu'un a ajouté à Object.prototype (une technique assez courante, de l'aveu même de l'auteur de l'article) verront leur code se briser entre les mains de quelqu'un d'autre.

0 votes

Le problème ici est que les types intégrés ne sont pas immuables. C'est un fait de Javascript. Cela ne signifie pas que les modifications d'Object.prototype ou de for... dans le code sont cassées. Cela signifie qu'il faut faire attention.

2 votes

@entonio : Je considérerais la malléabilité des types intégrés comme une fonctionnalité plutôt qu'un problème. C'est en grande partie ce qui rend les shims/polyfills réalisables. Sans cela, nous serions soit en train d'envelopper tous les types intégrés avec des sous-types éventuellement incompatibles, soit en train d'attendre le support universel des navigateurs (qui pourrait ne jamais arriver, quand les navigateurs ne supportent pas les choses parce que les gens ne les utilisent pas parce que les navigateurs ne les supportent pas). Comme les types intégrés peuvent être modifiés, nous pouvons simplement ajouter la plupart des fonctions qui n'existent pas encore.

83voto

BGerrissen Points 9274

Procurez-vous un exemplaire de ' Modèles de conception JavaScript par Dustin Diaz . Quelques chapitres sont consacrés à l'implémentation d'interfaces JavaScript par le biais de Duck Typing. C'est également une bonne lecture. Mais non, il n'y a pas d'implémentation native d'une interface en langage, 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
}

0 votes

La méthode décrite dans le livre "pro javascript design patterns" est probablement la meilleure approche parmi tout ce que j'ai lu ici et ce que j'ai essayé. Vous pouvez utiliser l'héritage par-dessus, ce qui permet de suivre encore mieux les concepts de la POO. Certains pourraient prétendre que vous n'avez pas besoin des concepts de la POO en JS, mais je ne suis pas d'accord.

22voto

Steven de Salas Points 4611

JavaScript (ECMAScript édition 3) dispose d'une fonction implements mot réservé mis de côté pour une utilisation future . Je pense que c'est exactement le but recherché, cependant, dans la précipitation de la sortie de la spécification, ils n'ont pas eu le temps de définir ce qu'il fallait en faire, donc, à l'heure actuelle, les navigateurs ne font rien d'autre que de le laisser là et de se plaindre occasionnellement si vous essayez de l'utiliser pour quelque chose.

Il est possible et même assez facile de créer votre propre Object.implement(Interface) avec une logique qui s'affole lorsqu'un ensemble particulier de propriétés/fonctions n'est pas implémenté dans un objet donné.

J'ai écrit un article sur 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'aborder ce sujet, mais c'est la logique que j'ai utilisée pour ma propre mise en œuvre de l'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 qu'il faut ajouter une méthode "implement" à la méthode Function.prototype ce qui peut poser un problème à certaines personnes, mais je trouve que ça marche très bien.

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

5 votes

Cette syntaxe me fait vraiment mal au cerveau, mais l'implémentation ici est assez intéressante.

2 votes

Javascript est voué à faire cela (blesser le cerveau), surtout lorsqu'il provient d'implémentations de langages OO plus propres.

12 votes

@StevendeSalas : Eh. JS tend en fait à être assez propre quand on arrête d'essayer de le traiter comme un langage orienté classe. Toutes les conneries nécessaires pour émuler les classes, les interfaces, etc... c'est ce qui va se passer. vraiment faire mal au cerveau. Des prototypes ? C'est simple, vraiment, une fois que vous arrêtez de les combattre.

14voto

Cody Points 1198

Interfaces JavaScript :

Bien que JavaScript ne pas ont le interface type, elle 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 d'assurer des interfaces cohérentes entre les classes, mais c'est possible et fréquemment émulé.

À l'heure actuelle, il existe une poignée de façons particulières d'émuler les interfaces en JavaScript ; la variation des approches satisfait généralement certains besoins, tandis que d'autres restent sans réponse. Souvent, l'approche la plus robuste est trop encombrante et bloque l'implémenteur (développeur).

Voici une approche des interfaces/classes abstraites qui n'est pas très lourde, qui est explicative, qui limite au maximum les implémentations à l'intérieur des abstractions et qui laisse 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

El resolvePrecept est une fonction utilitaire et une fonction d'aide à utiliser à l'intérieur de l'application Classe abstraite . Son rôle est de permettre une mise en œuvre personnalisée de l'encapsulation. Préceptes (données et comportement) . Il peut lancer des erreurs ou des avertissements -- ET -- attribuer une valeur par défaut à la classe Implementor.

iAbstractClass

El iAbstractClass définit l'interface à utiliser. Son approche implique un accord tacite avec sa classe Implementor. Cette interface attribue à chaque précepte vers le même espace de nom de précepte exact -- OU -- vers ce que le Résolveur de préceptes les retours de fonction. Cependant, l'accord tacite se résout à un contexte -- une disposition de Implementor.

Metteur en œuvre

L'implémenteur est simplement "d'accord" avec une interface ( iAbstractClass dans ce cas) et l'applique par l'utilisation de la fonction Détournement de constructeur : iAbstractClass.apply(this) . En définissant les données et le comportement ci-dessus, et ensuite détournement de le constructeur de l'interface -- en passant le contexte de Implementor au constructeur de l'interface -- nous pouvons nous assurer que les surcharges de Implementor seront ajoutées, et que l'interface explicitera les avertissements et les valeurs par défaut.

Il s'agit d'une approche très peu contraignante qui a très bien servi mon équipe et moi-même au fil du temps et des différents projets. Toutefois, elle comporte quelques réserves et inconvénients.

Inconvénients

Bien que cela aide à mettre en place une cohérence dans l'ensemble de votre logiciel dans une large mesure, cela ne met pas en œuvre de véritables interfaces -- mais les émule. Bien que les définitions, les valeurs par défaut, et les avertissements ou erreurs son explicité, l'explication de l'utilisation est appliqué et affirmé par le développeur (comme pour une grande partie du développement JavaScript).

C'est apparemment la meilleure approche pour "Interfaces en JavaScript" Cependant, j'aimerais que les points suivants soient résolus :

  • Affirmations des types de retour
  • Affirmations de signatures
  • Geler les objets de delete actions
  • Affirmations de tout autre élément prévalant 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-même.

6voto

Alex Reitbort Points 9120

Vous avez besoin d'interfaces en Java car le typage est statique et le contrat entre les classes doit être connu lors de la compilation. En JavaScript, c'est différent. JavaScript est typée dynamiquement ; cela signifie que lorsque vous obtenez l'objet, vous pouvez simplement vérifier s'il possède une méthode spécifique et l'appeler.

1 votes

En fait, vous n'avez pas besoin d'interfaces en Java, c'est une sécurité pour s'assurer que les objets ont une certaine API afin de pouvoir les remplacer par d'autres implémentations.

3 votes

Non, ils sont en fait nécessaires à Java pour qu'il puisse construire 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 requises par cette interface. Sinon, le compilateur devrait répartir les méthodes par nom au moment de l'exécution (comme le font les langages à typage dynamique).

0 votes

Je ne pense pas que ce soit correct. La répartition est toujours dynamique en Java (sauf si une méthode est finale), et le fait que la méthode appartienne à une interface ne change pas les règles de recherche. La raison pour laquelle les interfaces sont nécessaires dans les langages à typage statique est que vous pouvez utiliser le même "pseudo-type" (l'interface) pour vous référer à des classes non liées.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X