JavaScript'in arayüz türü var mı (Java'nın 'arayüzü' gibi)?


Yanıtlar:


649

"Bu sınıfın bu işlevlere sahip olması gerekir" (yani kendi başına arabirim yok) diye bir kavram yoktur, çünkü:

  1. JavaScript devralma, sınıflara değil nesnelere dayanır. Siz fark edene kadar bu çok önemli değil:
  2. JavaScript son derece dinamik olarak yazılan bir dildir - uygun yöntemlerle bir nesne oluşturabilir, bu da arabirime uymasını sağlayabilir ve ardından onu uygun hale getiren tüm şeyleri tanımlayabilir . Tür sistemini yıkmak çok kolay olurdu - yanlışlıkla bile! - ilk etapta bir tip sistemi yapmaya ve denemeye değmez.

Bunun yerine, JavaScript ördek yazma denen şeyi kullanır . (Ördek gibi yürürse ve ördek gibi quack yaparsa, JS'nin umduğu kadarıyla bir ördek.) Nesnenizde quack (), walk () ve fly () yöntemleri varsa, kod beklediği her yerde kullanabilir bazı "Ördeklenebilir" arayüzlerin uygulanmasına gerek kalmadan yürüyebilen, vaktinden kaçabilen ve uçabilen bir nesne. Arayüz, kodun kullandığı işlevler kümesidir (ve bu işlevlerden gelen dönüş değerleri) ve ördek yazarken, bunu ücretsiz olarak alırsınız.

Şimdi, aramaya çalışırsanız, kodunuzun yarısında başarısız olmayacağı anlamına gelmez some_dog.quack(); bir TypeError alırsınız. Açıkçası, köpeklere quack yapmalarını söylüyorsanız, biraz daha büyük sorunlarınız var; Ördek yazmak, tüm ördeklerinizi üst üste tuttuğunuzda en iyi sonucu verir, tabiri caizse, genel hayvan olarak muamele etmedikçe köpeklerin ve ördeklerin birbirine karışmasına izin vermeyin. Diğer bir deyişle, arayüz akışkan olmasına rağmen hala orada; genellikle bir köpeği ilk etapta quack ve uçmayı bekleyen kodlara geçirmek bir hatadır.

Ancak doğru şeyi yaptığınızdan eminseniz, kullanmaya başlamadan önce belirli bir yöntemin varlığını test ederek quacking-dog problemi üzerinde çalışabilirsiniz. Gibi bir şey

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

Böylece, kullanmadan önce kullanabileceğiniz tüm yöntemleri kontrol edebilirsiniz. Ancak sözdizimi biraz çirkin. Biraz daha güzel bir yol var:

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

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

Bu standart JavaScript, bu yüzden kullanmaya değer herhangi bir JS yorumlayıcıda çalışması gerekir. İngilizce gibi okuma ek bir yararı vardır.

Modern tarayıcılar için (yani, IE 6-8 dışında hemen hemen tüm tarayıcılar), mülkün görünmesini engellemenin bir yolu bile vardır for...in:

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

Sorun, IE7 nesnelerinin hiç olmaması .definePropertyve IE8'de, yalnızca ana nesnelerde (yani DOM öğeleri ve benzeri) çalıştığı iddia ediliyor. Uyumluluk bir sorunsa kullanamazsınız .defineProperty. (IE6'dan bile bahsetmeyeceğim, çünkü artık Çin dışında oldukça alakasız.)

Başka bir sorun, bazı kodlama stillerinin herkesin kötü kod yazdığını varsaymak ve Object.prototypebirinin körü körüne kullanmak istemesi durumunda değiştirilmesini yasaklamaktır for...in. Bunu önemsiyorsanız veya (IMO bozuk ) kodunu kullanıyorsanız, biraz farklı bir sürüm deneyin:

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

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

7
Olduğu kadar korkunç değil. for...inbu tür tehlikelerle doludur - ve her zaman böyle olmuştur ve bunu en azından birisinin Object.prototype(bu makalenin kendi kabulü ile nadir olmayan bir teknik) eklediğini düşünmeden yapan herkes , kodlarının başka birinin eline geçeceğini görecektir.
cHao

1
@entonio: Yerleşik türlerin biçimlendirilebilirliğini bir sorundan ziyade bir özellik olarak değerlendirirdim . Şimleri / çoklu dolguları mümkün kılan şeyin büyük bir parçasıdır. Onsuz, ya tüm yerleşik türleri muhtemelen uyumsuz alt türlerle sararız ya da evrensel tarayıcı desteğini beklerdik (tarayıcılar bir şeyleri desteklemediğinde insanlar bunu kullanmadığından asla gelmeyebilir) t destekleyin). Yerleşik türler değiştirilebilir olduğundan, bunun yerine henüz var olmayan birçok işlevi ekleyebiliriz.
cHao

1
JavaScript (1.8.5) son sürümünde olduğu bir nesnenin özelliğini tanımlayabilirsiniz değil enumerable. Bu şekilde for...insorunu önleyebilirsiniz . developer.mozilla.org/tr-TR/docs/Web/JavaScript/Reference/…
Tomas Prado

1
@ Tomás: Ne yazık ki, her tarayıcı ES5 ile uyumlu bir şey çalışana kadar, hala böyle şeyler hakkında endişelenmemiz gerekiyor. Ve o zaman bile, " for...insorun" hala bir dereceye kadar var olacak, çünkü her zaman özensiz kod olacak ... şey, bu ve Object.defineProperty(obj, 'a', {writable: true, enumerable: false, value: 3});sadece biraz daha fazla iş obj.a = 3;. İnsanları daha sık yapmaya çalışmayanları tamamen anlayabiliyorum. : P
Chao

1
Hehe ... "Açıkçası, köpeklere şaka yapmasını söylüyorsanız, biraz daha büyük sorunlarınız var. Dillerin aptallıktan kaçınmaya çalışmaması gerektiğini gösteren büyük benzetme. Bu her zaman kayıp bir savaştır. -Scott
Skooppa. com

72

Dustin Diaz'ın ' JavaScript tasarım modellerinin ' bir kopyasını alın . Duck Typing ile JavaScript arayüzleri uygulamaya adanmış birkaç bölüm var. Ayrıca güzel bir okuma. Ama hayır, bir arayüzün yerel dil uygulaması yok, Duck Type'a ihtiyacınız var .

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

"Pro javascript tasarım desenleri" kitabında açıklanan yöntem muhtemelen burada okuduğum ve denediklerimden bir sürü şey en iyi yaklaşımdır. Üstünde kalıtım kullanabilirsiniz, bu da OOP kavramlarını takip etmeyi daha da iyi hale getirir. Bazıları JS'de OOP kavramlarına ihtiyacınız olmadığını iddia edebilir, ancak farklı olmaya yalvarıyorum.
animageofmine

21

JavaScript (ECMAScript edition 3), ileride kullanılmak üzere kaydedilmiş , implementsayrılmış bir kelimeye sahiptir . Bunun tam olarak bu amaç için tasarlandığını düşünüyorum, ancak spesifikasyonu kapıdan çıkarmak için acele ederek, onunla ne yapacağını tanımlamak için zamanları yoktu, bu yüzden şu anda tarayıcılar başka bir şey yapmıyor orada oturmasına izin verin ve ara sıra bir şey için kullanmaya çalışırsanız şikayet edin.

Object.implement(Interface)Belirli bir özellik / işlev kümesi belirli bir nesneye uygulanmadığında şaşırtıcı olan kendi yönteminizi oluşturmak mümkün ve gerçekten kolaydır .

Kendi gösterimi aşağıdaki gibi kullandığım nesne yönelimi üzerine bir makale yazdım :

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

Bu kediyi kaplamanın birçok yolu var, ama bu benim kendi Arayüz uygulamam için kullandığım mantık. Bu yaklaşımı tercih ettiğimi görüyorum ve okumak ve kullanmak kolaydır (yukarıda gördüğünüz gibi). Bu, Function.prototypebazı insanların sorun yaşayabileceği bir 'uygulama' yöntemi eklemek anlamına gelir , ancak bunun güzel çalıştığını düşünüyorum.

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

4
Bu sözdizimi gerçekten beynimi incitiyor, ancak buradaki uygulama oldukça ilginç.
Cypher

2
Javascript, daha temiz OO dil uygulamalarından gelirken bunu (beyne zarar veren) yapmak zorundadır.
Steven de Salas

10
@StevendeSalas: Eh. JS, sınıf odaklı bir dil gibi davranmayı bıraktığınızda aslında oldukça temiz olma eğilimindedir. Sınıfları, arayüzleri vb. Taklit etmek için gereken tüm saçmalıklar ... beyninizi gerçekten incitecek olan budur. Prototipler? Basit şeyler, gerçekten, onlarla savaşmayı bıraktığın zaman.
cHao

"// .. Üyenin mantığını kontrol et." ? bu neye benziyor
PositiveGuy

Merhaba @ Biz, üye mantığı kontrol istenen özellikleri arasında döngü ve eksik varsa bir hata atma anlamına gelir .. satırları boyunca bir şey var interf = arguments[i]; for (prop in interf) { if (this.prototype[prop] === undefined) { throw 'Member [' + prop + '] missing from class definition.'; }}. Daha ayrıntılı bir örnek için makale bağlantısının altına bakın .
Steven de Salas

12

JavaScript Arayüzleri:

JavaScript belirtildiyse de değil sahip interfacetipini, genellikle gerekli katıdır. JavaScript'in dinamik doğası ve Prototip-Kalıtım kullanımı ile ilgili nedenlerden ötürü, sınıflar arasında tutarlı arabirimler sağlamak zordur - ancak bunu yapmak mümkündür; ve sıklıkla taklit edilir.

Bu noktada, JavaScript'te Arayüzleri taklit etmenin birkaç yolu vardır; yaklaşımlardaki varyans genellikle bazı ihtiyaçları karşılarken, diğerleri unutulmadan bırakılır. Çoğu zaman, en sağlam yaklaşım aşırı derecede zahmetlidir ve uygulayıcıyı (geliştirici) sabitler.

İşte arayüzler / Soyut Sınıflar için çok hantal olmayan, açıklayıcı, Soyutlamalar içindeki uygulamaları minimumda tutan ve dinamik veya özel metodolojiler için yeterli alan bırakan bir yaklaşım:

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

Katılımcılar

Precept Çözücü

resolvePreceptİşlevi içinde kullanmak için bir yardımcı program ve yardımcı fonksiyonudur Özet Class . Görevi, kapsüllenmiş kuralların (veri ve davranış) özel olarak uygulanmasına izin vermektir . Hatalar atabilir veya Uygulayıcı sınıfına varsayılan bir değer atayabilir - VE -.

iAbstractClass

Kullanılacak iAbstractClassarabirimi tanımlar. Yaklaşımı, Uygulayıcı sınıfıyla örtük bir anlaşma gerektirir. Bu arabirim, her bir precept'i Precept Resolver işlevinin döndürdüğü her ne olursa olsun aynı precept ad alanına - VEYA - atar . Ancak zımni anlaşma, bir bağlam - Uygulayıcı hükmü ile sonuçlanır.

Uygulatıcı

Uygulayıcı sadece bir Arabirim ( bu durumda iAbstractClass ) ile 'hemfikirdir' ve Yapıcı-Hijacking : kullanarak uygular iAbstractClass.apply(this). Yukarıdaki verileri ve davranışı tanımlayıp ardından Arabirim yapıcısını (Uygulayıcı'nın bağlamını Arabirim yapıcısına geçirerek) ele geçirerek, Uygulayıcının geçersiz kılmalarının eklenmesini ve Arabirimin uyarıları ve varsayılan değerleri açıklamasını sağlayabiliriz.

Bu, ekibime ve zamana ve farklı projelere çok iyi hizmet eden çok hantal bir yaklaşım. Ancak, bazı uyarılar ve dezavantajları vardır.

Dezavantajları

Bu, yazılımınız boyunca tutarlılığın önemli ölçüde uygulanmasına yardımcı olsa da , gerçek arayüzleri uygulamaz - ancak bunları taklit eder. Tanımlar, varsayılan ve uyarı veya hata olsa edilir irdelenmiş, kullanım izahati edilir zorlanan & iddia (JavaScript gelişme çok olduğu gibi) geliştirici tarafından.

Bu görünüşe göre "JavaScript Arayüzleri" için en iyi yaklaşımdır , ancak aşağıdakilerin çözüldüğünü görmek isterim:

  • Geri dönüş türlerinin iddiaları
  • İmza iddiaları
  • Nesneleri deleteeylemlerden dondurma
  • JavaScript topluluğunun özgünlüğünde yaygın olan veya gereken başka herhangi bir şeyin beyanları

Bununla birlikte, umarım bu, ekibim ve ben kadar size yardımcı olur.


7

Statik olarak yazıldığından ve sınıflar arasındaki sözleşmenin derleme sırasında bilinmesi gerektiğinden Java'da arabirimlere ihtiyacınız vardır. JavaScript'te farklıdır. JavaScript dinamik olarak yazılmıştır; bu, nesneyi aldığınızda belirli bir yöntemin olup olmadığını kontrol edip çağırabileceğiniz anlamına gelir.


1
Aslında, Java'da arayüzlere ihtiyacınız yoktur, nesnelerin belirli bir API'ye sahip olmasını sağlamak başarısız bir güvencedir, böylece diğer uygulamalar için bunları değiştirebilirsiniz.
BGerrissen

3
Hayır, derleme zamanında bir arabirim uygulayan sınıflar için vtables oluşturabilmeleri için Java'da gerçekten gereklidir. Bir sınıfın bir arabirim uyguladığını bildirmek, derleyiciye o arabirim için gereken tüm yöntemlere işaretçiler içeren küçük bir yapı oluşturmasını bildirir. Aksi takdirde, çalışma zamanında ada göre gönderilmesi gerekir (dinamik olarak yazılan diller gibi).
Münih

Bunun doğru olduğunu düşünmüyorum. Java'da dağıtım her zaman dinamiktir (belki bir yöntem son olmadığı sürece) ve yöntemin bir arayüze ait olması arama kurallarını değiştirmez. Statik olarak yazılan dillerde arayüzlerin gerekli olmasının nedeni, ilişkisiz sınıflara atıfta bulunmak için aynı 'sözde tür'ü (arayüz) kullanabilmenizdir.
entonio

2
@entonio: Dispatch göründüğü kadar dinamik değil. Gerçek yöntem, polimorfizm sayesinde çalışma süresine kadar genellikle bilinmemektedir, ancak bayt kodu "Metodunuzu çağırınız" demez; "Superclass.yourMethod'u çağır" yazıyor. JVM, hangi sınıfı arayacağını bilmeden bir yöntemi çağıramaz. Bağlama sırasında yourMethod, Superclass's vtable' daki # 5 girişine ve kendine ait her alt sınıf için yourMethod, sadece alt sınıfın # 5 girişine işaret edebilir uygun uygulamada.
cHao

1
@entonio: arayüzleri için kurallar do biraz değiştirin. (Değil languagewise, üretilen bayt kodu ve JVM arama süreci ama farklıdır.) Adında bir sınıf Implementationuygular o SomeInterfacesadece bu bütün arabirimini uygulayan söylemez. "Ben uyguluyorum SomeInterface.yourMethod" diyen ve yöntem tanımına işaret eden bilgiler var Implementation.yourMethod. JVM aradığında SomeInterface.yourMethod, bu arabirimin yönteminin uygulamaları hakkında bilgi arar ve çağırması gerektiğini bulur Implementation.yourMethod.
cHao

6

Umarım, hala bir cevap arayan herkes yardımcı olur.

Proxy (ECMAScript 2015'ten beri standarttır) kullanmayı deneyebilirsiniz: 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;
    }
});

Sonra kolayca şunu söyleyebilirsiniz:

myMap = {}
myMap.position = latLngLiteral;

5

Bir transcompiler kullanmak istediğinizde, TypeScript'i deneyebilirsiniz. Kahve ya da babil gibi dillerin yaptığı taslak ECMA özelliklerini (teklifte arayüzlere " protokoller " denir ) destekler.

TypeScript'te arayüzünüz aşağıdaki gibi görünebilir:

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

Ne yapamazsın:


3

JavaScript'te yerel arayüzler yoktur, bir arayüzü simüle etmenin birkaç yolu vardır. bunu yapan bir paket yazdım

implantasyonu burada görebilirsiniz


2

Javascript'in arayüzü yok. Ancak ördek tipi olabilir, burada bir örnek bulabilirsiniz:

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


Bu bağlantıdaki makalenin tür hakkında iddialarda bulunmak için kullandığı deseni seviyorum. Bir şey onun gerekiyordu yöntemi uygulamadığı zaman atılmış bir hata tam olarak beklediğim şeydir ve bu şekilde yaparsanız bu gerekli yöntemleri birlikte (bir arayüz gibi) nasıl gruplandırabilirim.
Eric Dubé

1
Aktarımdan (ve hata ayıklama için kaynak haritalarından) nefret ediyorum, ancak Typescript, ES6'ya çok yakın, burnumu tutmaya ve Typescript'e dalmaya meyilliyim. ES6 / Typescript ilginçtir, çünkü bir arabirimi (davranış) tanımlarken yöntemlere ek olarak özellikler eklemenize izin verir.
Reinsbrain

1

Bunun eski bir şey olduğunu biliyorum, ancak son zamanlarda kendimi arayüzlere karşı nesneleri kontrol etmek için kullanışlı bir API'ye ihtiyacım olduğunu gördüm. Bu yüzden şunu yazdım: https://github.com/tomhicks/methodical

NPM üzerinden de kullanılabilir: npm install methodical

Temel olarak yukarıda önerilen her şeyi yapar, biraz daha katı olmak için bazı seçenekler ve hepsi bir sürü if (typeof x.method === 'function')kazan plakası yapmak zorunda kalmadan .

Umarım birisi faydalı bulur.


Tom, yeni bir AngularJS TDD videosu izledim ve bir çerçeve yüklediğinde, bağımlı paketlerden biri sizin metodik paketiniz! Aferin!
Cody

Haha mükemmel. Temelde çalışan insanlar beni JavaScript arayüzleri hareketsiz olduğuna ikna ettikten sonra terk ettim. Son zamanlarda temelde bir arabirimin ne olduğu sadece belirli yöntemlerin kullanıldığından emin olmak için bir nesneyi vekaleten bir kütüphane hakkında bir fikrim vardı. Hala arayüzlerin JavaScript'te bir yeri olduğunu düşünüyorum! Bu videoyu bu arada bağlayabilir misin? Bir göz atmak istiyorum.
Tom

Eminim Tom. Yakında bulmaya çalışacağım. Thx vekiller olarak arayüzler hakkındaki fıkra. Şerefe!
Cody

1

Bu eski bir soru, yine de bu konu beni rahatsız etmekten asla vazgeçmiyor.

Buradaki ve web'deki yanıtların birçoğu arayüzün "uygulanmasına" odaklandığından, alternatif bir görünüm önermek isterim:

Benzer şekilde davranan birden fazla sınıf kullandığımda (yani bir arabirim uygulamak ) arayüzlerin eksikliğini en çok hissediyorum .

Örneğin , bölümlerin içeriğini ve HTML nasıl "bilmek" e-posta bölümleri fabrikaları almak için bekleyen bir e- posta oluşturucu var. Bu nedenle, hepsinin bir çeşit getContent(id)ve getHtml(content)yöntemlere sahip olması gerekir .

Arayüzlere en yakın desen (yine de bir çözüm olsa da) 2 arabirim yöntemini tanımlayacak 2 bağımsız değişken alacak bir sınıf kullanmaktır.

Bu kalıpla ilgili temel zorluk, yöntemlerin staticözelliklerine erişmek için ya örnek olması ya da örneğin argümanı olarak alınması gerektiğidir . Ancak bu değiş tokuşu zahmete değdiğim durumlar var.

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


0

böyle soyut bir arayüz

const MyInterface = {
  serialize: () => {throw "must implement serialize for MyInterface types"},
  print: () => console.log(this.serialize())
}

örnek oluştur:

function MyType() {
  this.serialize = () => "serialized "
}
MyType.prototype = MyInterface

ve kullan

let x = new MyType()
x.print()
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.