Есть ли в JavaScript тип интерфейса (например, Java-интерфейс)? - PullRequest
291 голосов
/ 14 сентября 2010

Я учу , как сделать ООП с помощью JavaScript .Есть ли у него концепция интерфейса (например, Java interface)?

Так я бы смог создать слушателя ...

Ответы [ 10 ]

577 голосов
/ 14 сентября 2010

Нет понятия "этот класс должен иметь эти функции" (то есть интерфейсов как таковых), потому что:

  1. Наследование JavaScript основано на объектах, а не на классах. Это не имеет большого значения, пока вы не поймете:
  2. JavaScript - это чрезвычайно динамически типизированный язык - вы можете создать объект с надлежащими методами, которые бы привели его в соответствие с интерфейсом, , а затем отменить определение всего, что привело его . Было бы так легко подорвать систему типов - даже случайно! - что не стоит пытаться создать систему типов.

Вместо этого в JavaScript используется то, что называется duck typing . (Если он ходит как утка и крякает как утка, то, что касается JS, это утка.) Если ваш объект имеет методы quack (), walk () и fly (), код может использовать его везде, где он ожидает объект, который может ходить, крякать и летать, не требуя реализации какого-либо "Duckable" интерфейса. Интерфейс - это в точности набор функций, которые использует код (и возвращаемые значения из этих функций), и с помощью утки вы получаете это бесплатно.

Это не значит, что ваш код не потерпит неудачу на полпути, если вы попытаетесь вызвать some_dog.quack(); вы получите TypeError. Честно говоря, если вы говорите собакам крякать, у вас есть немного большие проблемы; Печатание утки работает лучше всего, когда вы, так сказать, держите всех своих уток в ряд и не позволяете собакам и уткам смешиваться, если вы не относитесь к ним как к обычным животным. Другими словами, даже если интерфейс плавный, он все еще там; часто бывает ошибкой передавать собаку в код, который ожидает, что она сначала будет крякать и летать.

Но если вы уверены, что поступаете правильно, вы можете обойти проблему собаки-кряка, проверив наличие определенного метода, прежде чем пытаться его использовать. Что-то вроде

if (typeof(someObject.quack) == "function")
{
    // This thing can quack
}

Таким образом, вы можете проверить все методы, которые вы можете использовать, прежде чем использовать их. Синтаксис довольно уродливый, хотя. Есть немного более красивый способ:

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

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

Это стандартный JavaScript, поэтому он должен работать в любом интерпретаторе JS, который стоит использовать. Это имеет дополнительное преимущество чтения как английский.

Для современных браузеров (то есть практически для любого браузера, кроме IE 6-8), есть даже способ не отображать свойство в for...in:

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

Проблема в том, что у объектов IE7 вообще нет .defineProperty, а в IE8 он предположительно работает только с хост-объектами (то есть с элементами DOM и т. Д.). Если проблема совместимости, вы не можете использовать .defineProperty. (Я даже не буду упоминать IE6, потому что он больше не имеет значения за пределами Китая.)

Другая проблема заключается в том, что некоторые стили кодирования любят предполагать, что все пишут плохой код, и запрещают изменять Object.prototype в случае, если кто-то хочет слепо использовать for...in. Если вы заботитесь об этом или используете код (IMO broken ), который это делает, попробуйте немного другую версию:

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

if (can(someObject, "quack"))
{
    someObject.quack();
}
66 голосов
/ 14 сентября 2010

Возьмите копию шаблонов проектирования JavaScript ' Дастин Диаз Есть несколько глав, посвященных реализации интерфейсов JavaScript через Duck Typing. Это тоже приятно читать. Но нет, языковой реализации интерфейса не существует, вам нужно 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
}
19 голосов
/ 09 октября 2011

JavaScript (ECMAScript edition 3) содержит implements зарезервированное слово , сохраненное для будущего использования .Я думаю, что это предназначено именно для этой цели, однако, в спешке, чтобы вывести спецификацию за дверь, у них не было времени, чтобы определить, что с ней делать, поэтому в настоящее время браузеры ничего не делают, кромепусть он сидит там и иногда жалуется, если вы пытаетесь использовать его для чего-то.

Возможно и действительно достаточно просто создать свой собственный метод Object.implement(Interface) с логикой, которая искажает всякий раз, когда конкретный набор свойств / функцийне реализовано в данном объекте.

Я написал статью о объектно-ориентации , где использую мою собственную запись следующим образом :

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

Есть много способов обшить эту конкретную кошку, но эту логику я использовал для своей реализации интерфейса.Я считаю, что предпочитаю такой подход, и его легко читать и использовать (как вы можете видеть выше).Это означает добавление метода 'Implement' к Function.prototype, с которым у некоторых людей могут возникнуть проблемы, но я считаю, что он прекрасно работает.

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;
}
12 голосов
/ 19 декабря 2013

Интерфейсы JavaScript:

Хотя JavaScript имеет , а не имеет тип interface, это часто требуется.По причинам, связанным с динамической природой JavaScript и использованием Prototypical-Inheritance, трудно обеспечить согласованные интерфейсы между классами - однако это возможно;и часто эмулируется.

На данный момент есть несколько конкретных способов эмулировать интерфейсы в JavaScript;Разница в подходах обычно удовлетворяет некоторые потребности, в то время как другие остаются без внимания.Часто самый надежный подход является чрезмерно громоздким и мешает разработчику (разработчику).

Вот подход к интерфейсам / абстрактным классам, который не очень громоздок, является объяснительным, сводит реализации внутри абстракций к минимумуи оставляет достаточно места для динамических или нестандартных методологий:

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

Участники

Rescept Resolver

resolvePrecept function - это служебная и вспомогательная функция, которая используется внутри вашего Abstract Class .Его работа заключается в том, чтобы обеспечить настраиваемую обработку реализации инкапсулированных Precepts (данные и поведение) .Он может выдавать ошибки или предупреждать - И - назначать значение по умолчанию для класса Implementor.

iAbstractClass

iAbstractClass определяетинтерфейс, который будет использоваться.Его подход влечет за собой молчаливое соглашение с классом Implementor.Этот интерфейс назначает каждое предписание одному и тому же точному пространству имен предписания - ИЛИ - тому, что возвращает функция Precept Resolver .Однако молчаливое соглашение разрешает контекст - положение Исполнителя.

Исполнитель

Исполнитель просто«соглашается» с интерфейсом ( iAbstractClass в данном случае) и применяет его с помощью Constructor-Hijacking : iAbstractClass.apply(this).Определяя данные и поведение выше, а затем угон конструктора интерфейса - передавая контекст реализатора конструктору интерфейса - мы можем гарантировать, что переопределения реализатора будут добавлены, и что интерфейс будет объяснять предупреждения и значения по умолчанию.

Это очень неуклюжий подход, который послужил моей команде и мне очень хорошо в течение времени и различных проектов.Тем не менее, у него есть некоторые недостатки и недостатки.

Недостатки

Хотя это помогает в значительной степени обеспечить согласованность всего программного обеспечения, оно делаетне реализовывать истинные интерфейсы - но эмулирует их.Хотя определения, значения по умолчанию, а также предупреждения или ошибки объяснены, объяснение использования принудительно поддерживается разработчиком (как и в большинстве случаев разработки JavaScript).

Это, на первый взгляд, лучший подход к «Интерфейсам в JavaScript» , однако мне бы хотелось, чтобы было решено следующее:

  • Утверждения типов возврата
  • Утверждения подписей
  • Замораживание объектов из delete действий
  • Утверждения чего-либо еще преобладающего или необходимого в специфичностисообщество JavaScript

Тем не менее, я надеюсь, что это поможет вам так же, как и моей команде и мне.

5 голосов
/ 26 июля 2017

Надеюсь, что любой, кто все еще ищет ответ, найдет его полезным.

Вы можете попробовать использовать прокси-сервер (это стандартно с 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;
    }
});

Тогда вы можете легко сказать:

myMap = {}
myMap.position = latLngLiteral;
4 голосов
/ 14 сентября 2010

Вам нужны интерфейсы в Java, так как он статически типизирован и контракт между классами должен быть известен во время компиляции. В JavaScript это отличается. JavaScript динамически типизируется; это означает, что когда вы получаете объект, вы можете просто проверить, есть ли у него определенный метод, и вызвать его.

2 голосов
/ 05 июня 2018

Если вы хотите использовать транскомпилятор, вы можете попробовать TypeScript. Он поддерживает черновые функции ECMA, аналогичные языкам, таким как coffeescript или babel.

В TypeScript ваш интерфейс может выглядеть следующим образом:

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

Что вы не можете сделать:

  • Определить шаблоны RegExp для значения типа
  • Определить проверку как длину строки
  • Числовые диапазоны
  • и т.д.
2 голосов
/ 05 марта 2015

Javascript не имеет интерфейсов.Но это может быть напечатано уткой, пример можно найти здесь:

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

1 голос
/ 11 апреля 2019

нет собственных интерфейсов в JavaScript, Есть несколько способов симулировать интерфейс. я написал пакет, который делает это

вы можете увидеть имплантацию здесь

1 голос
/ 20 января 2014

Я знаю, что это старый, но в последнее время я все больше и больше нуждался в удобном API для проверки объектов по интерфейсам. Итак, я написал это: https://github.com/tomhicks/methodical

Также доступно через NPM: npm install methodical

Он в основном делает все, что предложено выше, с некоторыми вариантами, чтобы быть немного более строгим, и все без необходимости загружать шаблон if (typeof x.method === 'function').

Надеюсь, кто-то найдет это полезным.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...