O JavaScript tem o tipo de interface (como a 'interface' do Java)?

324

Estou aprendendo a fazer OOP com JavaScript . Possui o conceito de interface (como o Java interface)?

Então, eu seria capaz de criar um ouvinte ...

Tom Brito
fonte
18
Para quem procura mais opções, o TypeScript possui interfaces .
24416 SD
2
Outra opção se você quiser usar baunilha JS é implement.js , como demonstrado aqui
Richard Lovell

Respostas:

650

Não há noção de "essa classe deve ter essas funções" (ou seja, nenhuma interface em si), porque:

  1. A herança do JavaScript é baseada em objetos, não em classes. Isso não é grande coisa até você perceber:
  2. O JavaScript é uma linguagem de tipo extremamente dinâmico - você pode criar um objeto com os métodos adequados, o que o tornaria compatível com a interface e , em seguida, indefinir todas as coisas que o fizeram . Seria tão fácil subverter o sistema de tipos - mesmo que acidentalmente! - que não valeria a pena tentar criar um sistema de tipos em primeiro lugar.

Em vez disso, o JavaScript usa o que é chamado de digitação de pato . (Se ele anda como um pato e grasna como um pato, na medida em que JS se importa, é um pato.) Se o seu objeto possui métodos quack (), walk () e fly (), o código pode usá-lo onde quer que ele espere um objeto que pode andar, grasnar e voar, sem exigir a implementação de alguma interface "Duckable". A interface é exatamente o conjunto de funções que o código usa (e os valores de retorno dessas funções) e, com a digitação do duck, você obtém isso de graça.

Agora, isso não significa que seu código não falhará no meio, se você tentar ligar some_dog.quack(); você receberá um TypeError. Francamente, se você está dizendo para os cães gritarem, você tem problemas um pouco maiores; digitar patos funciona melhor quando você mantém todos os seus patos em ordem, por assim dizer, e não deixa cães e patos se misturarem, a menos que os trate como animais genéricos. Em outras palavras, mesmo que a interface seja fluida, ela ainda está lá; geralmente é um erro passar um cachorro para o código que espera gritar e voar em primeiro lugar.

Mas se você tem certeza de que está fazendo a coisa certa, pode solucionar o problema dos cães quacking testando a existência de um método específico antes de tentar usá-lo. Algo como

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

Portanto, você pode verificar todos os métodos que pode usar antes de usá-los. A sintaxe é meio feia, no entanto. Há uma maneira um pouco mais bonita:

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

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

Como o JavaScript é padrão, deve funcionar em qualquer interpretador JS que valha a pena usar. Tem o benefício adicional de ler como o inglês.

Para navegadores modernos (ou seja, praticamente qualquer navegador que não seja o IE 6-8), existe até uma maneira de impedir que a propriedade apareça em for...in:

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

O problema é que os objetos do IE7 não possuem .defineProperty, e no IE8, ele supostamente funciona apenas em objetos host (ou seja, elementos DOM e outros). Se a compatibilidade for um problema, você não poderá usar .defineProperty. (Eu nem vou mencionar o IE6, porque é mais irrelevante fora da China.)

Outra questão é que alguns estilos de codificação gostam de assumir que todos escrevem códigos incorretos e proíbem a modificação Object.prototypecaso alguém queira usar cegamente for...in. Se você se importa com isso, ou está usando um código (IMO quebrado ), tente uma versão ligeiramente diferente:

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

if (can(someObject, "quack"))
{
    someObject.quack();
}
cHao
fonte
7
Não é tão horrível quanto parece. for...iné - e sempre foi - repleto de tais perigos, e qualquer pessoa que faça isso sem pelo menos considerar que alguém adicionado a Object.prototype(uma técnica não incomum, pela própria admissão desse artigo) verá seu código quebrar nas mãos de outra pessoa.
cHao 14/09/10
1
@entonio: Eu consideraria a maleabilidade dos tipos internos como um recurso e não um problema. É uma grande parte do que viabiliza os calços / polyfills. Sem ele, estaríamos agrupando todos os tipos internos com subtipos possivelmente incompatíveis ou aguardando o suporte universal ao navegador (o que talvez nunca aconteça, quando navegadores não suportam coisas porque as pessoas não o usam, porque os navegadores não ' não suportá-lo). Como os tipos internos podem ser modificados, podemos adicionar apenas muitas das funções que ainda não existem.
cHao 11/02
1
Na última versão do Javascript (1.8.5), você pode definir a propriedade de um objeto para não ser enumerável. Dessa forma, você pode evitar o for...inproblema. developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/…
Tomas Prado
1
@ Tomás: Infelizmente, até que todo navegador esteja executando algo compatível com o ES5, ainda precisamos nos preocupar com coisas assim. E mesmo assim, o " for...inproblema" ainda existirá até certo ponto, porque sempre haverá código desleixado ... bem, isso e Object.defineProperty(obj, 'a', {writable: true, enumerable: false, value: 3});é um pouco mais trabalhoso do que apenas obj.a = 3;. Eu posso entender totalmente as pessoas que não tentam fazer isso com mais frequência. : P
cHao
1
Hehe ... adoro o "Francamente, se você está dizendo para os cães gritarem, você tem problemas um pouco maiores. Grande analogia para mostrar que os idiomas não devem estar tentando evitar a estupidez. Essa é sempre uma batalha perdida. -Scott
Skooppa. com
73

Pegue uma cópia dos ' JavaScript design patterns ' de Dustin Diaz . Existem alguns capítulos dedicados à implementação de interfaces JavaScript por meio do Duck Typing. É uma boa leitura também. Mas não, não há implementação nativa de linguagem de uma interface, você precisa do 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
}
BGerrissen
fonte
O método descrito no livro "padrões de design pro javascript" é provavelmente a melhor abordagem para várias coisas que li aqui e pelo que tentei. Você pode usar a herança em cima dela, o que torna ainda melhor seguir os conceitos de POO. Alguns podem afirmar que você não precisa de conceitos de POO em JS, mas eu imploro para diferir.
animageofmine
21

JavaScript (ECMAScript edição 3) tem uma implementspalavra reservada salva para uso futuro . Eu acho que isso é planejado exatamente para esse propósito, no entanto, com a pressa de divulgar as especificações, eles não tiveram tempo de definir o que fazer com elas; portanto, atualmente, os navegadores não fazem nada além deixe-o ali e, ocasionalmente, reclame se tentar usá-lo para alguma coisa.

É possível e de fato fácil criar seu próprio Object.implement(Interface)método com uma lógica que falha sempre que um conjunto específico de propriedades / funções não é implementado em um determinado objeto.

Escrevi um artigo sobre orientação a objetos em que use minha própria notação da seguinte maneira :

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

Existem várias maneiras de tratar desse gato em particular, mas essa é a lógica que usei para minha própria implementação de interface. Acho que prefiro essa abordagem e é fácil de ler e usar (como você pode ver acima). Significa adicionar um método 'implementar' ao Function.prototypequal algumas pessoas podem ter problemas, mas acho que funciona lindamente.

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;
}
Steven de Salas
fonte
4
Essa sintaxe realmente machuca meu cérebro, mas a implementação aqui é bastante interessante.
cifra
2
Javascript é obrigado a fazer isso (prejudicando o cérebro), especialmente quando proveniente de implementações de linguagem OO mais limpas.
Steven de Salas
10
@StevendeSalas: Eh. JS realmente tende a ser bastante limpo quando você para de tentar tratá-lo como uma linguagem orientada a classe. Toda a porcaria necessária para imitar classes, interfaces, etc ... é isso que realmente fará seu cérebro doer. Protótipos? Coisas simples, realmente, quando você para de lutar contra eles.
Chao
o que há em "// .. Verifique a lógica do membro". ? como é isso?
PositiveGuy
Oi @We, verificar a lógica dos membros significa percorrer as propriedades desejadas e gerar um erro se estiver faltando .. algo ao longo das linhas de var interf = arguments[i]; for (prop in interf) { if (this.prototype[prop] === undefined) { throw 'Member [' + prop + '] missing from class definition.'; }}. Veja a parte inferior do link do artigo para um exemplo mais elaborado.
Steven de Salas
12

Interfaces JavaScript:

Embora o JavaScript não tenha esse interfacetipo, muitas vezes é necessário. Por razões relacionadas à natureza dinâmica do JavaScript e ao uso de herança prototípica, é difícil garantir interfaces consistentes entre as classes - no entanto, é possível fazê-lo; e frequentemente emulado.

Neste ponto, existem várias maneiras específicas de emular interfaces em JavaScript; a variação nas abordagens geralmente satisfaz algumas necessidades, enquanto outras não são atendidas. Muitas vezes, a abordagem mais robusta é excessivamente complicada e atrapalha o implementador (desenvolvedor).

Aqui está uma abordagem para interfaces / classes abstratas que não é muito complicada, é explicativa, mantém as implementações dentro de abstrações em um nível mínimo e deixa espaço suficiente para metodologias dinâmicas ou personalizadas:

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

Participantes

Resolvedor de Preceitos

A resolvePreceptfunção é uma função utilitário e auxiliar para usar dentro da sua Classe Abstrata . Seu trabalho é permitir a manipulação de implementação personalizada de Preceitos encapsulados (dados e comportamento) . Ele pode gerar erros ou avisar - E - atribuir um valor padrão à classe Implementor.

iAbstractClass

O iAbstractClassdefine a interface a ser usada. Sua abordagem envolve um acordo tácito com sua classe Implementor. Essa interface atribui cada preceito ao mesmo espaço de nome exato de preceito - OU - para o que a função Resolver do Preceito retornar. No entanto, o acordo tácito resolve para um contexto - uma provisão do Implementador.

Implementor

O Implementor simplesmente 'concorda' com uma Interface ( iAbstractClass neste caso) e aplica-lo pelo uso de Construtor-Hijacking : iAbstractClass.apply(this). Ao definir os dados e o comportamento acima e, em seguida, seqüestrar o construtor da Interface - passando o contexto do implementador para o construtor da interface - podemos garantir que as substituições do implementador sejam adicionadas e que a interface explique avisos e valores padrão.

Essa é uma abordagem muito incômoda, que serviu muito bem à minha equipe e ao longo do tempo e a diferentes projetos. No entanto, ele tem algumas ressalvas e desvantagens.

Desvantagens

Embora isso ajude a implementar consistentemente todo o software em um grau significativo, ele não implementa interfaces verdadeiras - mas simula-as. Embora as definições, os padrões e os avisos ou erros sejam explicados, a explicação do uso é imposta e afirmada pelo desenvolvedor (como ocorre com grande parte do desenvolvimento do JavaScript).

Essa é aparentemente a melhor abordagem para "Interfaces em JavaScript" , no entanto, eu adoraria ver o seguinte resolvido:

  • Asserções de tipos de retorno
  • Asserções de assinaturas
  • Congelar objetos de deleteações
  • Asserções de qualquer outra coisa predominante ou necessária na especificidade da comunidade JavaScript

Dito isto, espero que isso ajude você tanto quanto a minha equipe e a mim.

Cody
fonte
7

Você precisa de interfaces em Java, pois ele é digitado estaticamente e o contrato entre as classes deve ser conhecido durante a compilação. Em JavaScript é diferente. JavaScript é digitado dinamicamente; significa que, quando você obtém o objeto, pode apenas verificar se ele possui um método específico e chamá-lo.

Alex Reitbort
fonte
1
Na verdade, você não precisa de interfaces em Java, é uma falha de segurança garantir que os objetos tenham uma determinada API para que você possa trocá-los por outras implementações.
BGerrissen
3
Não, eles são realmente necessários em Java para que possa criar vtables para classes que implementam uma interface em tempo de compilação. Declarar que uma classe implementa uma interface instrui o compilador a criar uma pequena estrutura que contém ponteiros para todos os métodos necessários para essa interface. Caso contrário, ele teria que despachar pelo nome no tempo de execução (como fazem as linguagens dinamicamente tipadas).
munificent
Eu não acho isso correto. A expedição é sempre dinâmica em java (a menos que talvez um método seja final), e o fato de o método pertencer a uma interface não altera as regras de pesquisa. A razão pela qual as interfaces são necessárias em linguagens de tipo estaticamente é para que você possa usar o mesmo 'pseudo-tipo' (a interface) para se referir a classes não relacionadas.
entonio 18/01/2013
2
@entonio: o envio não é tão dinâmico quanto parece. O método atual geralmente não é conhecido até o tempo de execução, graças ao polimorfismo, mas o bytecode não diz "invoke yourMethod"; diz "invoque Superclass.yourMethod". A JVM não pode invocar um método sem saber em qual classe procurá-lo. Durante o vínculo, ele pode colocar yourMethoda entrada nº 5 na Superclasstabela de tabelas e, para cada subclasse que possui sua própria yourMethod, simplesmente aponta a entrada nº 5 da subclasse. na implementação apropriada.
precisa saber é
1
@entonio: Para interfaces, as regras não mudam um pouco. (Não no idioma, mas o bytecode gerado e o processo de pesquisa da JVM são diferentes.) Uma classe chamada Implementationque implementa SomeInterfacenão apenas diz que implementa toda a interface. Possui informações que dizem "eu implemento SomeInterface.yourMethod" e aponta para a definição de método para Implementation.yourMethod. Quando a JVM chama SomeInterface.yourMethod, ela procura na classe informações sobre implementações do método dessa interface e acha que precisa chamar Implementation.yourMethod.
Chao
6

Espero que qualquer pessoa que ainda esteja procurando uma resposta seja útil.

Você pode experimentar usando um Proxy (é padrão desde 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;
    }
});

Então você pode facilmente dizer:

myMap = {}
myMap.position = latLngLiteral;
Shaedrich
fonte
5

Quando você deseja usar um transcompilador, tente o TypeScript. Ele suporta recursos de rascunho do ECMA (na proposta, as interfaces são chamadas de " protocolos ") semelhantes às linguagens como coffeescript ou babel.

No TypeScript, sua interface pode se parecer com:

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

O que você não pode fazer:

Shaedrich
fonte
3

não há interfaces nativas no JavaScript, existem várias maneiras de simular uma interface. eu escrevi um pacote que faz isso

você pode ver a implantação aqui

Amit Wagner
fonte
2

Javascript não possui interfaces. Mas pode ser do tipo pato, um exemplo pode ser encontrado aqui:

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

Reinsbrain
fonte
Gosto do padrão que o artigo desse link usa para fazer afirmações sobre o tipo. Um erro sendo lançado quando algo não implementa o método que é suposto é exatamente o que eu esperaria, e eu gosto de como posso agrupar esses métodos necessários (como uma interface) se eu fizer dessa maneira.
Eric Dubé
1
Eu odeio transpilar (e mapas de origem para depuração), mas o Typescript está tão próximo do ES6 que estou inclinado a segurar meu nariz e mergulhar no Typescript. O ES6 / Typescript é interessante porque permite incluir propriedades além dos métodos ao definir uma interface (comportamento).
Reinsbrain
1

Sei que é antigo, mas recentemente me vi precisando cada vez mais de uma API útil para verificar objetos em relação a interfaces. Então eu escrevi isso: https://github.com/tomhicks/methodical

Também está disponível via NPM: npm install methodical

Basicamente, faz tudo o que foi sugerido acima, com algumas opções para ser um pouco mais rigoroso e tudo sem ter que fazer um monte de if (typeof x.method === 'function')clichê.

Espero que alguém ache útil.

Tom
fonte
Tom, acabei de assistir a um vídeo do AngularJS TDD e quando ele instala uma estrutura, um dos pacotes dependentes é o seu pacote metódico! Bom trabalho!
Cody
Haha excelente. Eu basicamente o abandonei depois que as pessoas no trabalho me convenceram de que as interfaces em JavaScript eram um impedimento. Recentemente, tive uma idéia sobre uma biblioteca que basicamente copia um objeto para garantir que apenas certos métodos sejam usados, o que é basicamente o que é uma interface. Eu ainda acho que as interfaces têm um lugar no JavaScript! Você pode vincular esse vídeo a propósito? Eu gostaria de dar uma olhada.
Tom
Você aposta, Tom. Vou tentar encontrá-lo em breve. Thx a anedota sobre interfaces como proxies, também. Felicidades!
Cody
1

Esta é uma pergunta antiga, mas esse tópico nunca deixa de me incomodar.

Como muitas das respostas aqui e em toda a Web se concentram em "impor" a interface, eu gostaria de sugerir uma visão alternativa:

Sinto a falta de interfaces quando estou usando várias classes que se comportam de maneira semelhante (por exemplo, implementar uma interface ).

Por exemplo, eu tenho um Gerador de E - mail que espera receber Fábricas de Seções de E-mail , que "sabem" como gerar o conteúdo e o HTML das seções. Assim, eles toda a necessidade de ter algum tipo de getContent(id)e getHtml(content)métodos.

O padrão mais próximo das interfaces (embora ainda seja uma solução alternativa) que eu poderia pensar é usar uma classe que terá 2 argumentos, que definirão os 2 métodos de interface.

O principal desafio desse padrão é que os métodos precisam ser static, ou obter como argumento a própria instância, para acessar suas propriedades. No entanto, há casos em que considero essa troca compensadora.

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

GalAbra
fonte
0

interface abstrata como esta

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

crie uma instância:

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

e use

let x = new MyType()
x.print()
Kevin Krausse
fonte