Uczę się, jak tworzyć OOP za pomocą JavaScript . Czy ma koncepcję interfejsu (np. Java interface
)?
Więc mógłbym stworzyć słuchacza ...
Uczę się, jak tworzyć OOP za pomocą JavaScript . Czy ma koncepcję interfejsu (np. Java interface
)?
Więc mógłbym stworzyć słuchacza ...
Odpowiedzi:
Nie ma pojęcia, że „ta klasa musi mieć te funkcje” (to znaczy brak interfejsów jako takich), ponieważ:
Zamiast tego JavaScript używa tak zwanego pisania kaczego . (Jeśli chodzi jak kaczka i kwakuje jak kaczka, jeśli chodzi o JS, jest to kaczka.) Jeśli twój obiekt ma metody quack (), walk () i fly (), kod może użyć go tam, gdzie się spodziewa obiekt, który może chodzić, kwakać i latać, bez konieczności implementacji jakiegoś interfejsu „Duckable”. Interfejs jest dokładnie zestawem funkcji, z których korzysta kod (i zwracanymi wartościami z tych funkcji), a przy wpisywaniu kaczki dostajesz to za darmo.
Nie oznacza to, że kod nie zawiedzie w połowie, jeśli spróbujesz zadzwonić some_dog.quack()
; otrzymasz TypeError. Szczerze mówiąc, jeśli każesz psom kwakać, masz nieco większe problemy; pisanie na kaczkach działa najlepiej, gdy trzymasz wszystkie kaczki w rzędzie, że tak powiem, i nie pozwalasz, aby psy i kaczki zlewały się ze sobą, chyba że traktujesz je jak zwierzęta ogólne. Innymi słowy, mimo że interfejs jest płynny, nadal tam jest; częstym błędem jest podanie psa do kodu, który oczekuje, że będzie kwakał i latał w pierwszej kolejności.
Ale jeśli masz pewność, że postępujesz właściwie, możesz obejść problem psiaka, sprawdzając, czy istnieje konkretna metoda, zanim spróbujesz jej użyć. Coś jak
if (typeof(someObject.quack) == "function")
{
// This thing can quack
}
Możesz więc sprawdzić wszystkie metody, których możesz użyć przed ich użyciem. Jednak składnia jest trochę brzydka. Jest nieco ładniejszy sposób:
Object.prototype.can = function(methodName)
{
return ((typeof this[methodName]) == "function");
};
if (someObject.can("quack"))
{
someObject.quack();
}
Jest to standardowy JavaScript, więc powinien działać w każdym interpretatorze JS, którego warto użyć. Ma dodatkową zaletę czytania jak angielski.
W przypadku współczesnych przeglądarek (czyli praktycznie każdej przeglądarki innej niż IE 6-8) istnieje nawet sposób, aby nie wyświetlać się w for...in
:
Object.defineProperty(Object.prototype, 'can', {
enumerable: false,
value: function(method) {
return (typeof this[method] === 'function');
}
}
Problem polega na tym, że w ogóle nie ma obiektów IE7 .defineProperty
, a w IE8 podobno działa tylko na obiektach hosta (czyli elementach DOM i tym podobnych). Jeśli problemem jest kompatybilność, nie możesz jej użyć .defineProperty
. (Nie wspomnę nawet o IE6, ponieważ jest on raczej nieistotny poza Chinami.)
Inną kwestią jest to, że niektóre style kodowania zakładają, że każdy pisze zły kod, i zabraniają modyfikowania Object.prototype
w przypadku, gdy ktoś chce na ślepo używać for...in
. Jeśli Ci na tym zależy lub używasz ( zepsutego IMO ) kodu, który działa, wypróbuj nieco inną wersję:
function can(obj, methodName)
{
return ((typeof obj[methodName]) == "function");
}
if (can(someObject, "quack"))
{
someObject.quack();
}
for...in
jest - i zawsze był - obarczony takimi niebezpieczeństwami, a każdy, kto to zrobi, nie biorąc pod uwagę, że ktoś dodał do Object.prototype
(nietypowa technika, jak przyznaje ten artykuł), zobaczy, że jego kod łamie się w czyichś rękach.
for...in
problemu. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
for...in
problem” nadal będzie do pewnego stopnia istniał, ponieważ zawsze będzie niechlujny kod ... no cóż, i Object.defineProperty(obj, 'a', {writable: true, enumerable: false, value: 3});
to jest o wiele więcej pracy niż tylko obj.a = 3;
. Całkowicie rozumiem ludzi, którzy nie próbują tego robić częściej. : P
Odbierz kopię „ wzorców projektowych JavaScript ” autorstwa Dustina Diaza . Jest kilka rozdziałów poświęconych implementacji interfejsów JavaScript poprzez Duck Typing. To także miła lektura. Ale nie, nie ma natywnej implementacji interfejsu dla języka, musisz typ kaczki .
// 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 edycja 3) ma implements
zarezerwowane słowo zapisane do wykorzystania w przyszłości . Myślę, że jest to przeznaczone właśnie do tego celu, jednak w pośpiechu, aby wydostać się ze specyfikacji, nie mieli czasu na określenie, co z nią zrobić, więc w chwili obecnej przeglądarki nie robią nic poza pozwól mu tam usiąść i od czasu do czasu narzekać, jeśli spróbujesz użyć go do czegoś.
Możliwe jest i naprawdę łatwe stworzenie własnej Object.implement(Interface)
metody z logiką, która błąka się za każdym razem, gdy określony zestaw właściwości / funkcji nie jest zaimplementowany w danym obiekcie.
Napisałem artykuł o orientacji obiektowej, w którym stosuję własną notację w następujący sposób :
// 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);
Jest wiele sposobów na skórowanie tego konkretnego kota, ale taką logikę zastosowałem przy własnej implementacji interfejsu. Uważam, że wolę takie podejście i jest łatwe do odczytania i użycia (jak widać powyżej). Oznacza to dodanie metody „implementuj”, z Function.prototype
którą niektórzy mogą mieć problem, ale uważam, że działa ona pięknie.
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.'; }}
. Zobacz bardziej szczegółowy przykład na dole linku do artykułu .
Chociaż JavaScript nie ma tego interface
typu, często jest potrzebny. Z przyczyn związanych z dynamicznym charakterem JavaScript i wykorzystaniem Prototypical-Inheritance trudno jest zapewnić spójne interfejsy między klasami - jednak jest to możliwe; i często emulowane.
W tym momencie istnieje kilka konkretnych sposobów emulacji interfejsów w JavaScript; wariancja podejść zazwyczaj zaspokaja niektóre potrzeby, podczas gdy inne pozostają bez odpowiedzi. Często najbardziej niezawodne podejście jest zbyt uciążliwe i utrudnia implementację (programistę).
Oto podejście do interfejsów / klas abstrakcyjnych, które nie jest zbyt uciążliwe, objaśniające, ogranicza implementacje wewnątrz abstrakcji do minimum i pozostawia wystarczająco dużo miejsca na dynamiczne lub niestandardowe metodologie:
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
};
Precept Resolver
Ta resolvePrecept
funkcja jest funkcją narzędziową i pomocniczą do użycia wewnątrz klasy abstrakcyjnej . Jego zadaniem jest umożliwienie dostosowanej obsługi implementacji enkapsulowanych Wskazań (dane i zachowanie) . Może rzucać błędy lub ostrzegać - ORAZ - przypisywać wartość domyślną do klasy Implementor.
iAbstractClass
iAbstractClass
Określa interfejs jest używany. Jego podejście wymaga milczącej umowy z klasą Implementor. Ten interfejs przypisuje każde przykazanie dokładnie tej samej przestrzeni nazw przykazań - LUB - do wszystkiego, co zwraca funkcja Rozpoznanie przykazań . Jednak milcząca umowa rozwiązuje się w kontekście - postanowienie Implementora.
Realizator
Implementator po prostu „zgadza się” z interfejsem ( iAbstractClass w tym przypadku) i stosuje je przez użycie Konstruktor-Hijacking : iAbstractClass.apply(this)
. Poprzez zdefiniowanie powyższych danych i zachowania, a następnie przejęcie konstruktora interfejsu - przekazanie kontekstu implementatora do konstruktora interfejsu - możemy zagwarantować, że zostaną dodane przesłonięcia implementatora, a interfejs wyjaśni objaśnienia i wartości domyślne.
Jest to bardzo nieporęczne podejście, które służyło mojemu zespołowi i mnie bardzo dobrze na czas i różne projekty. Ma jednak pewne zastrzeżenia i wady.
Wady
Chociaż pomaga to w znacznym stopniu zaimplementować spójność w oprogramowaniu, nie implementuje prawdziwych interfejsów, ale je emuluje. Chociaż definicje, domyślne, a ostrzeżenia lub błędy są wyjaśniony, wyjaśnianiu obsługi jest egzekwowane i zapewnił przez programistę (podobnie jak w przypadku większości skryptów JavaScript).
To pozornie najlepsze podejście do „Interfejsów w JavaScript” , jednak chciałbym zobaczyć rozwiązanie:
delete
działaniamiTo powiedziawszy, mam nadzieję, że to pomoże ci tak bardzo, jak mój zespół i ja.
Potrzebujesz interfejsów w Javie, ponieważ jest on wpisany statycznie, a kontrakt między klasami powinien być znany podczas kompilacji. W JavaScript jest inaczej. JavaScript jest dynamicznie wpisywany; oznacza to, że po otrzymaniu obiektu możesz po prostu sprawdzić, czy ma określoną metodę i wywołać go.
yourMethod
pozycję 5 w Superclass
tabeli vtable, a dla każdej podklasy, która ma własną yourMethod
, po prostu wskazuje pozycję tej podklasy 5 przy odpowiednim wdrożeniu.
Implementation
która implementuje SomeInterface
, nie tylko mówi, że implementuje cały interfejs. Zawiera informacje z napisem „Wdrażam SomeInterface.yourMethod
” i wskazuje definicję metody dla Implementation.yourMethod
. Gdy JVM wywołuje SomeInterface.yourMethod
, szuka w klasie informacji o implementacjach metody tego interfejsu i stwierdza, że musi zadzwonić Implementation.yourMethod
.
Mam nadzieję, że każdy, kto wciąż szuka odpowiedzi, uzna ją za pomocną.
Możesz wypróbować za pomocą serwera proxy (jest to standard od 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;
}
});
Następnie możesz łatwo powiedzieć:
myMap = {}
myMap.position = latLngLiteral;
Jeśli chcesz użyć transkompilatora, możesz wypróbować TypeScript. Obsługuje projekty funkcji ECMA (we wniosku interfejsy nazywane są „ protokołami ”) ”) podobne do języków, takich jak coffeescript lub babel.
W TypeScript twój interfejs może wyglądać następująco:
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"
}
Czego nie możesz zrobić:
w JavaScript nie ma natywnych interfejsów, istnieje kilka sposobów symulacji interfejsu. napisałem paczkę, która to robi
możesz zobaczyć implantację tutaj
JavaScript nie ma interfejsów. Ale można go wpisać w kaczkę, przykład można znaleźć tutaj:
http://reinsbrain.blogspot.com/2008/10/interface-in-javascript.html
Wiem, że to stary, ale ostatnio odkryłem, że potrzebuję coraz więcej przydatnych narzędzi API do sprawdzania obiektów pod kątem interfejsów. Więc napisałem to: https://github.com/tomhicks/methodical
Jest również dostępny przez NPM: npm install methodical
Zasadniczo robi wszystko, co sugerowano powyżej, z pewnymi opcjami dotyczącymi bycia bardziej rygorystycznymi, a wszystko to bez konieczności wykonywania wielu zadań if (typeof x.method === 'function')
.
Mam nadzieję, że ktoś uzna to za przydatne.
To stare pytanie, ale ten temat nigdy nie przestaje mnie męczyć.
Ponieważ wiele odpowiedzi tutaj i w Internecie koncentruje się na „wymuszaniu” interfejsu, chciałbym zasugerować alternatywny widok:
Brak interfejsów najbardziej odczuwam, gdy korzystam z wielu klas, które zachowują się podobnie (tj. Implementują interfejs ).
Na przykład mam generator e-maili, który spodziewa się otrzymywać fabryki sekcji e-mail , które „wiedzą”, jak wygenerować zawartość sekcji i kod HTML. Dlatego wszyscy muszą mieć jakieś metody getContent(id)
i getHtml(content)
metody.
Najbliższy wzorzec interfejsów (choć wciąż jest to obejście), o którym mogłem pomyśleć, to użycie klasy, która otrzyma 2 argumenty, które zdefiniują 2 metody interfejsu.
Głównym wyzwaniem związanym z tym wzorcem jest to, że metody muszą albo być static
argumentem samej instancji, aby uzyskać dostęp do jej właściwości. Są jednak przypadki, w których uznaję to za kompromisowe.
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));
taki abstrakcyjny interfejs
const MyInterface = {
serialize: () => {throw "must implement serialize for MyInterface types"},
print: () => console.log(this.serialize())
}
utwórz instancję:
function MyType() {
this.serialize = () => "serialized "
}
MyType.prototype = MyInterface
i użyj go
let x = new MyType()
x.print()