Я учусь делать ООП с помощью JavaScript . Есть ли у него концепция интерфейса (например, Java interface
)?
Так что я бы смог создать слушателя ...
Я учусь делать ООП с помощью JavaScript . Есть ли у него концепция интерфейса (например, Java interface
)?
Так что я бы смог создать слушателя ...
Ответы:
Там нет понятия "этот класс должен иметь эти функции" (то есть, интерфейсы как таковые), потому что:
Вместо этого в JavaScript используется так называемая « утиная печать» . (Если он ходит как утка и крякает как утка, то, что касается 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 сломанный ) код, который делает, попробуйте немного другую версию:
function can(obj, methodName)
{
return ((typeof obj[methodName]) == "function");
}
if (can(someObject, "quack"))
{
someObject.quack();
}
for...in
это - и всегда было - чревато такими опасностями, и любой, кто делает это, по крайней мере, не считая, что кто-то добавил Object.prototype
(что весьма необычно, по собственному признанию этой статьи), увидит, как его код сломается в чужих руках.
for...in
проблемы. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
for...in
проблема» все еще будет существовать в некоторой степени, потому что всегда будет неаккуратный код ... ну, это, и Object.defineProperty(obj, 'a', {writable: true, enumerable: false, value: 3});
это немного больше работы, чем просто obj.a = 3;
. Я могу полностью понять людей, которые не пытаются делать это чаще. : P
Возьмите копию « Шаблоны дизайна 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
}
В 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);
Есть много способов обшить эту конкретную кошку, но эту логику я использовал для собственной реализации интерфейса. Я считаю, что предпочитаю этот подход, и его легко читать и использовать (как вы можете видеть выше). Это означает добавление метода «внедрить», с 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;
}
var interf = arguments[i]; for (prop in interf) { if (this.prototype[prop] === undefined) { throw 'Member [' + prop + '] missing from class definition.'; }}
. Смотрите в нижней части статьи ссылку для более подробного примера.
Хотя 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
};
Завет Резолвер
Эта resolvePrecept
функция является вспомогательной и вспомогательной функцией для использования внутри вашего абстрактного класса . Его работа заключается в том, чтобы обеспечить настраиваемую обработку реализации инкапсулированных Предписаний (данных и поведения) . Он может выдавать ошибки или предупреждать - И - назначать значение по умолчанию для класса Implementor.
iAbstractClass
iAbstractClass
Определяет интерфейс , который будет использоваться. Его подход влечет за собой молчаливое соглашение с его классом Implementor. Этот интерфейс назначает каждую заповедь одному и тому же пространству имен точных заповедей - ИЛИ - любому, что возвращает функция Rescept Resolver . Однако молчаливое соглашение разрешает контекст - положение исполнителя.
Implementor
Implementor просто «согласен» с интерфейсом ( iAbstractClass в данном случае) и применяет его с помощью Конструктора-Угон : iAbstractClass.apply(this)
. Определив данные и поведение выше, а затем перехватив конструктор интерфейса - передав контекст реализатора конструктору интерфейса - мы можем гарантировать, что переопределения реализатора будут добавлены и что интерфейс будет объяснять предупреждения и значения по умолчанию.
Это очень не обременительный подход, который послужил моей команде и мне очень хорошо в течение времени и различных проектов. Тем не менее, у него есть некоторые предостережения и недостатки.
Недостатки
Хотя это в значительной степени помогает обеспечить согласованность всего программного обеспечения, оно не реализует настоящие интерфейсы, а имитирует их. Хотя определения, по умолчанию, и предупреждения или ошибки являются пояснено, экспликация использования в исполнение и утверждается застройщиком (как и большая часть развития JavaScript).
Это, казалось бы, лучший подход к «Интерфейсам в JavaScript» , однако мне бы хотелось, чтобы было решено следующее:
delete
действийТем не менее, я надеюсь, что это поможет вам так же, как и моя команда и я.
Вам нужны интерфейсы в Java, так как он статически типизирован и контракт между классами должен быть известен во время компиляции. В JavaScript это отличается. JavaScript динамически типизируется; это означает, что когда вы получаете объект, вы можете просто проверить, есть ли у него определенный метод, и вызвать его.
yourMethod
в запись № 5 в Superclass
таблице vtable, а для каждого подкласса, который имеет свой собственный yourMethod
, просто указывает запись этого подкласса № 5. при соответствующей реализации.
Implementation
который реализует SomeInterface
, не просто говорит, что он реализует весь интерфейс. В нем есть информация, которая говорит «Я реализую SomeInterface.yourMethod
» и указывает на определение метода для Implementation.yourMethod
. Когда JVM вызывает SomeInterface.yourMethod
, он ищет в классе информацию о реализациях метода этого интерфейса и находит, что ему нужно вызвать Implementation.yourMethod
.
Надеюсь, что любой, кто все еще ищет ответ, найдет его полезным.
Вы можете попробовать использовать прокси (это стандартно с 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;
Если вы хотите использовать транскомпилятор, вы можете попробовать 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"
}
Что вы не можете сделать:
в JavaScript нет нативных интерфейсов, есть несколько способов симулировать интерфейс. я написал пакет, который делает это
вы можете увидеть имплантацию здесь
Javascript не имеет интерфейсов. Но это может быть напечатано уткой, пример можно найти здесь:
http://reinsbrain.blogspot.com/2008/10/interface-in-javascript.html
Я знаю, что это старый, но в последнее время я все больше и больше нуждался в удобном API для проверки объектов по интерфейсам. Поэтому я написал это: https://github.com/tomhicks/methodical
Это также доступно через NPM: npm install methodical
Он в основном делает все, что предложено выше, с некоторыми вариантами, чтобы быть немного более строгим, и все без необходимости if (typeof x.method === 'function')
загружать шаблон.
Надеюсь, кто-то найдет это полезным.
Это старый вопрос, тем не менее эта тема не перестает меня беспокоить.
Поскольку многие ответы здесь и в Интернете направлены на «усиление» интерфейса, я хотел бы предложить альтернативное представление:
Больше всего мне не хватает интерфейсов, когда я использую несколько классов, которые ведут себя одинаково (т.е. реализуют интерфейс ).
Например, у меня есть Генератор электронной почты, который ожидает получать фабрики разделов электронной почты , которые «знают», как генерировать содержимое разделов и HTML. Следовательно, все они должны иметь какие-то getContent(id)
и getHtml(content)
методы.
Самый близкий шаблон к интерфейсам (хотя это все еще обходной путь), о котором я мог подумать, это использование класса, который получит 2 аргумента, которые будут определять 2 метода интерфейса.
Основная проблема с этим шаблоном заключается в том, что методы должны быть static
или получить в качестве аргумента сам экземпляр, чтобы получить доступ к его свойствам. Однако есть случаи, в которых я считаю, что этот компромисс стоит хлопот.
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));
абстрактный интерфейс, как это
const MyInterface = {
serialize: () => {throw "must implement serialize for MyInterface types"},
print: () => console.log(this.serialize())
}
создать экземпляр:
function MyType() {
this.serialize = () => "serialized "
}
MyType.prototype = MyInterface
и использовать это
let x = new MyType()
x.print()