Я вчуся робити 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 в 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()