Я вчуся робити OOP за допомогою JavaScript . Чи є в ньому концепція інтерфейсу (наприклад, Java interface)?
Тож я міг би створити слухача ...
Я вчуся робити OOP за допомогою JavaScript . Чи є в ньому концепція інтерфейсу (наприклад, Java interface)?
Тож я міг би створити слухача ...
Відповіді:
Немає поняття "цей клас повинен мати ці функції" (тобто ніяких інтерфейсів сам по собі), оскільки:
Натомість JavaScript використовує те, що називається типом качок . (Якщо він ходить як качка, і стукає, як качка, наскільки це піклується JS, це качка.) Якщо у вашого об'єкта є методи крякання (), 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. Це також приємне читання. Але ні, немає інтерфейсної мовної реалізації інтерфейсу, ви повинні 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 (видання 3 ECMAScript) має 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 та використання прототипічного спадкування, важко забезпечити послідовні інтерфейси між класами - однак це можливо; і часто наслідується
На даний момент існує декілька певних способів емуляції інтерфейсів у 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
};
Рецепт Resolver
Ця resolvePreceptфункція - це програма утиліта та помічник, яка використовується всередині вашого абстрактного класу . Його завдання полягає в тому, щоб забезпечити індивідуальну обробку реалізацією інкапсульованих рецептів (даних та поведінки) . Він може видаляти помилки або попереджати - AND - присвоювати значення за замовчуванням класу Implementor.
iAb абстрактClass
iAbstractClassВизначає інтерфейс , який буде використовуватися. Її підхід тягне за собою мовчазну угоду з класом Implementor. Цей інтерфейс призначає кожному рецепту однакове точне простір імен рецептур - АБО - тому, що повертається функція Precept Resolver . Однак мовчазна угода узгоджується з контекстом - положенням Виконавця.
Виконавець
Implementor просто «згоден» з інтерфейсом ( iAbstractClass в даному випадку) і застосовує його за допомогою Конструктора-Угон : iAbstractClass.apply(this). Визначаючи дані та поведінку вище, а потім викрадаючи конструктор інтерфейсу - передаючи контекст впровадника конструктору інтерфейсу, - ми можемо забезпечити додавання переопределень впровадника та інтерфейс викласти попередження та значення за замовчуванням.
Це дуже громіздкий підхід, який дуже довго служив моїй команді та я дуже добре працював у різних проектах. Однак у нього є деякі застереження та недоліки.
Недоліки
Хоча це значною мірою допомагає впроваджувати послідовність у вашому програмному забезпеченні, воно не реалізує справжні інтерфейси - але імітує їх. Хоча визначення, по замовчуванню, і попередження або помилки є пояснено, експлікація використання в виконання і затверджується забудовником (як і велика частина розвитку JavaScript).
Це, здається, найкращий підхід до "Інтерфейсів у JavaScript" , проте я б хотів, щоб це було вирішено:
deleteдійЦе сказало, я сподіваюся, що це допоможе вам настільки, наскільки це має моя команда та я.
Інтерфейси в Java вам потрібні, оскільки вони набрані статично і контракт між класами повинен бути відомий під час компіляції. У JavaScript це інше. JavaScript динамічно набирається; це означає, що коли ви отримаєте об'єкт, ви можете просто перевірити, чи є у нього певний метод, і викликати його.
yourMethodпід записом № 5 в Superclassvtable, а для кожного підкласу, який має свій власний 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()