O que é a interface Java equivalente em Ruby?


101

Podemos expor interfaces em Ruby como fazemos em java e aplicar os módulos ou classes Ruby para implementar os métodos definidos por interface.

Uma maneira é usar a herança e o método_missing para obter o mesmo, mas existe alguma outra abordagem mais apropriada disponível?



6
Você deve se perguntar duas vezes por que você ainda precisa disso. Freqüentemente, interfaces suficientes são usadas apenas para compilar alguma coisa, o que não é um problema no Ruby.
Arnis Lapsa

1
Esta questão pode ou não ser considerada uma duplicata de [ Em Ruby, o que é equivalente a uma interface em C #? ] ( StackOverflow.Com/q/3505521/#3507460 ).
Jörg W Mittag

2
Por que preciso disso? Eu quero implementar algo que você pode chamar de "versionável", o que torna os documentos / arquivos versionáveis, mas versionáveis ​​usando o que ... Por exemplo, posso torná-los versionáveis ​​usando softwares de repositório existentes como SVN ou CVS. Qualquer que seja o mecanismo subjacente que eu escolher, ele deve fornecer algumas funções mínimas básicas. Eu quero usar a interface como coisa para impor a implementação dessas funções mínimas por qualquer nova implementação de repositório subjacente.
crazycrv de

Sandi Metz em seu livro POODR usa testes para documentar interfaces. Vale muito a pena ler este livro. Em 2015, eu diria que a resposta @ aleksander-pohl é a melhor.
Greg Dan,

Respostas:


85

Ruby tem interfaces como qualquer outra linguagem.

Observe que você deve ter cuidado para não confundir o conceito de Interface , que é uma especificação abstrata das responsabilidades, garantias e protocolos de uma unidade com o conceito de interfaceque é uma palavra-chave na programação Java, C # e VB.NET línguas. Em Ruby, usamos o primeiro o tempo todo, mas o último simplesmente não existe.

É muito importante distinguir os dois. O que é importante é a interface , não o interface. O não interfacediz nada de útil. Nada demonstra isso melhor do que as interfaces de marcador em Java, que são interfaces que não possuem nenhum membro: basta dar uma olhada em java.io.Serializablee java.lang.Cloneable; esses dois interfacesignificam coisas muito diferentes, embora tenham exatamente a mesma assinatura.

Então, se dois interfaces que significam coisas diferentes têm a mesma assinatura, o que exatamente o interfacepar está garantindo a você?

Outro bom exemplo:

package java.util;

interface List<E> implements Collection<E>, Iterable<E> {
    void add(int index, E element)
        throws UnsupportedOperationException, ClassCastException,
            NullPointerException, IllegalArgumentException,
            IndexOutOfBoundsException;
}

Qual é a interface de java.util.List<E>.add?

  • que o comprimento da coleção não diminua
  • que todos os itens que estavam na coleção antes ainda estão lá
  • que elementestá na coleção

E qual desses realmente aparece no interface? Nenhum! Não há nada no interfaceque diz que o Addmétodo deve mesmo adicionar , ele também pode remover um elemento da coleção.

Esta é uma implementação perfeitamente válida de que interface:

class MyCollection<E> implements java.util.List<E> {
    void add(int index, E element)
        throws UnsupportedOperationException, ClassCastException,
            NullPointerException, IllegalArgumentException,
            IndexOutOfBoundsException {
        remove(element);
    }
}

Outro exemplo: onde java.util.Set<E>realmente diz que é, você sabe, um conjunto ? Lugar algum! Ou mais precisamente, na documentação. Em inglês.

Em praticamente todos os casos de interfaces, tanto do Java quanto do .NET, todas as informações relevantes estão na verdade nos documentos, não nos tipos. Portanto, se os tipos não dizem nada de interessante, por que mantê-los? Por que não ficar apenas com a documentação? E é exatamente isso que Ruby faz.

Observe que existem outras linguagens nas quais a Interface pode realmente ser descrita de uma forma significativa. No entanto, essas linguagens normalmente não chamam a construção que descreve a Interface " interface", eles a chamam type. Em uma linguagem de programação de tipo dependente, você pode, por exemplo, expressar as propriedades de que uma sortfunção retorna uma coleção do mesmo comprimento que o original, que cada elemento que está no original também está na coleção classificada e que nenhum elemento maior aparece antes de um elemento menor.

Resumindo: Ruby não tem equivalente a Java interface. Ele faz , no entanto, tem um equivalente a um Java interface , e é exatamente o mesmo que em Java: documentação.

Além disso, assim como em Java, os testes de aceitação também podem ser usados ​​para especificar interfaces .

Em particular, em Ruby, a Interface de um objeto é determinada pelo que ele pode fazer , não o que classé ou o que moduleele se mistura. Qualquer objeto que tenha um <<método pode ser anexado. Isso é muito útil em testes de unidade, onde você pode simplesmente passar um Arrayou um em Stringvez de um mais complicado Logger, embora Arraye Loggernão compartilhe um explícito, interfaceexceto pelo fato de que ambos têm um método chamado <<.

Outro exemplo é StringIO, que implementa a mesma interface como IOe, portanto, uma grande porção da interface de File, mas sem qualquer partilha ancestral comum além Object.


279
Embora seja uma boa leitura, não acho a resposta tão útil. Parece uma dissertação sobre por que interfaceé inútil, perdendo o sentido de seu uso. Teria sido mais fácil dizer que ruby ​​é digitado dinamicamente e que tem um foco diferente em mente e torna conceitos como IOC desnecessários / indesejados. É uma mudança difícil se você está acostumado com o projeto por contrato. Algo de que Rails poderia se beneficiar, que a equipe principal percebeu como você pode ver nas versões mais recentes.
goliatone

12
Pergunta de acompanhamento: qual é a melhor maneira de documentar uma interface em Ruby? Uma palavra interface- chave Java pode não fornecer todas as informações relevantes, mas fornece um local óbvio para colocar a documentação. Eu escrevi uma classe em Ruby que implementa (suficiente) IO, mas fiz isso por tentativa e erro e não fiquei muito feliz com o processo. Também escrevi várias implementações de uma interface minha, mas documentar quais métodos são necessários e o que eles devem fazer para que outros membros de minha equipe possam criar implementações provou ser um desafio.
Patrick

9
A interface construção é de fato necessária apenas para tratar tipos diferentes como iguais em linguagens de herança única com tipagem estática (por exemplo, tratar LinkedHashSete ArrayListambos como a Collection), não tem praticamente nada a ver com Interface como mostra esta resposta. Ruby não é tipado estaticamente, portanto, não há necessidade para a construção .
Esailija

16
Eu li isso como "algumas interfaces não fazem sentido, portanto as interfaces são ruins. Por que você iria querer usar interfaces?". Isso não responde à pergunta e, francamente, apenas soa como alguém que não entende para que servem as interfaces e quais são seus benefícios.
Oddman

13
Seu argumento sobre a invalidade da interface List, citando um método que realiza uma remoção em uma função chamada "add", é um exemplo clássico de um argumento reductio ad absurdum. Em particular, é possível em qualquer linguagem (ruby incluído) escrever um método que faça algo diferente do que o esperado. Este não é um argumento válido contra "interface", é apenas um código inválido.
Justin Ohms

58

Experimente os "exemplos compartilhados" de rspec:

https://www.relishapp.com/rspec/rspec-core/v/3-5/docs/example-groups/shared-examples

Você escreve uma especificação para sua interface e então coloca uma linha nas especificações de cada implementador, por exemplo.

it_behaves_like "my interface"

Exemplo completo:

RSpec.shared_examples "a collection" do
  describe "#size" do
    it "returns number of elements" do
      collection = described_class.new([7, 2, 4])
      expect(collection.size).to eq(3)
    end
  end
end

RSpec.describe Array do
  it_behaves_like "a collection"
end

RSpec.describe Set do
  it_behaves_like "a collection"
end

Atualização : Oito anos depois (2020), ruby ​​agora tem suporte para interfaces tipadas estaticamente via sorvete. Consulte Classes e interfaces abstratas na documentação do sorbet.


15
Eu acredito que essa deve ser a resposta aceita. Esta é a maneira que a maioria das linguagens fracas de tipo pode fornecer interfaces semelhantes às do Java. O aceito explica porque Ruby não possui interfaces, não como emulá-las.
SystematicFrank

1
Eu concordo, esta resposta me ajudou muito mais como um desenvolvedor Java migrando para Ruby do que a resposta aceita acima.
Cam

Sim, mas o ponto principal de uma interface é que ela tem os mesmos nomes de método, mas as classes concretas têm que ser as únicas para implementar o comportamento, o que provavelmente é diferente. Então, o que devo testar no exemplo compartilhado?
Rob Wise

Ruby torna tudo pragmático. Se você gostaria de ter um código documentado e bem escrito, adicione testes / especificações e isso será uma espécie de verificação de tipagem estática.
Dmitry Polushkin

41

Podemos expor interfaces em Ruby como fazemos em java e aplicar os módulos ou classes Ruby para implementar os métodos definidos por interface.

Ruby não tem essa funcionalidade. Em princípio, ele não precisa deles, pois o Ruby usa o que é chamado de digitação de pato .

Existem algumas abordagens que você pode seguir.

Escreva implementações que levantem exceções; se uma subclasse tentar usar o método não implementado, ela falhará

class CollectionInterface
  def add(something)
    raise 'not implemented'
  end
end

Junto com o acima, você deve escrever um código de teste que reforce seus contratos (o que outro post aqui chama incorretamente de Interface )

Se você se pega escrevendo métodos vazios, como o tempo todo, então escreva um módulo auxiliar que capture

module Interface
  def method(name)
    define_method(name) { |*args|
      raise "interface method #{name} not implemented"
    }
  end
end

class Collection
  extend Interface
  method :add
  method :remove
end

Agora, combine o acima com os módulos Ruby e você estará perto do que deseja ...

module Interface
  def method(name)
    define_method(name) { |*args|
      raise "interface method #{name} not implemented"
    }
  end
end

module Collection
  extend Interface
  method :add
  method :remove
end

col = Collection.new # <-- fails, as it should

E então você pode fazer

class MyCollection
  include Collection

  def add(thing)
    puts "Adding #{thing}"
  end
end

c1 = MyCollection.new
c1.add(1)     # <-- output 'Adding 1'
c1.remove(1)  # <-- fails with not implemented

Deixe-me enfatizar mais uma vez: isso é rudimentar, pois tudo em Ruby acontece em tempo de execução; não há verificação de tempo de compilação. Se você combinar isso com o teste, poderá detectar erros. Ainda mais, se você levar o exposto acima, provavelmente poderá escrever uma Interface que execute a verificação da classe na primeira vez que um objeto dessa classe for criado; tornando seus testes tão simples quanto chamar MyCollection.new... sim, por cima :)


Ok, mas se seu Collection = MyCollection implementar um método não definido na Interface, isso funciona perfeitamente, então você não pode garantir que seu Objeto tenha apenas as definições de métodos de Interface.
Joel AZEMAR

Isso é incrível, obrigado. A digitação Duck é boa, mas às vezes é bom comunicar explicitamente a outros desenvolvedores como uma interface deve se comportar.
Mirodinho 08/01/19

10

Como todos aqui disseram, não existe um sistema de interface para Ruby. Mas, por meio da introspecção, você mesmo pode implementá-lo facilmente. Aqui está um exemplo simples que pode ser melhorado de várias maneiras para ajudá-lo a começar:

class Object
  def interface(method_hash)
    obj = new
    method_hash.each do |k,v|
      if !obj.respond_to?(k) || !((instance_method(k).arity+1)*-1)
        raise NotImplementedError, "#{obj.class} must implement the method #{k} receiving #{v} parameters"
      end
    end
  end
end

class Person
  def work(one,two,three)
    one + two + three
  end

  def sleep
  end

  interface({:work => 3, :sleep => 0})
end

Remover um dos métodos declarados em Person ou alterar o número de argumentos gerará a NotImplementedError.


5

Não existem interfaces no modo Java. Mas há outras coisas que você pode desfrutar no ruby.

Se você quiser implementar algum tipo de tipo e interface - de forma que os objetos possam ser verificados se eles têm alguns métodos / mensagens que você deseja deles -, você pode então dar uma olhada em rubycontracts . Ele define um mecanismo semelhante aos PyProtocols . Um blog sobre verificação de tipos em ruby ​​está aqui .

As abordagens mencionadas não são projetos vivos, embora o objetivo pareça bom no início, parece que a maioria dos desenvolvedores de ruby ​​pode viver sem verificação de tipo estrita. Mas a flexibilidade do ruby ​​permite implementar a verificação de tipo.

Se você deseja estender objetos ou classes (a mesma coisa no ruby) por certos comportamentos ou de alguma forma ter o método ruby ​​de herança múltipla, use o mecanismo includeou extend. Com includevocê pode incluir métodos de outra classe ou módulo em um objeto. Com extendvocê pode adicionar comportamento a uma classe, de forma que suas instâncias tenham os métodos adicionados. Essa foi uma explicação muito curta, no entanto.

Em minha opinião, a melhor maneira de resolver a necessidade da interface Java é entender o modelo de objeto ruby ​​(consulte as palestras de Dave Thomas, por exemplo). Provavelmente você esquecerá as interfaces Java. Ou você tem um aplicativo excepcional em sua programação.


Essas palestras de Dave Thomas estão atrás de um paywall.
Purplejacket de

5

Como muitas respostas indicam, não há como em Ruby forçar uma classe a implementar um método específico, herdando de uma classe, incluindo um módulo ou algo semelhante. A razão para isso é provavelmente a prevalência de TDD na comunidade Ruby, que é uma maneira diferente de definir a interface - os testes não apenas especificam as assinaturas dos métodos, mas também o comportamento. Portanto, se você quiser implementar uma classe diferente, que implemente alguma interface já definida, você deve ter certeza de que todos os testes foram aprovados.

Normalmente, os testes são definidos de forma isolada, usando simulações e stubs. Mas também existem ferramentas como o Bogus , que permite definir testes de contrato. Esses testes não apenas definem o comportamento da classe "primária", mas também verificam se os métodos fragmentados existem nas classes cooperantes.

Se você está realmente preocupado com interfaces em Ruby, eu recomendaria usar uma estrutura de teste que implementa testes de contrato.


3

Todos os exemplos aqui são interessantes, mas faltam a validação do contrato de Interface, quero dizer, se você deseja que seu objeto implemente todas as definições de métodos de Interface e somente estes você não pode. Portanto, proponho um exemplo rápido e simples (pode ser melhorado com certeza) para garantir que você tenha exatamente o que espera ter através de sua Interface (O contrato).

considere sua interface com os métodos definidos como esse

class FooInterface
  class NotDefinedMethod < StandardError; end
  REQUIRED_METHODS = %i(foo).freeze
  def initialize(object)
    @object = object
    ensure_method_are_defined!
  end
  def method_missing(method, *args, &block)
    ensure_asking_for_defined_method!(method)
    @object.public_send(method, *args, &block)
  end
  private
  def ensure_method_are_defined!
    REQUIRED_METHODS.each do |method|
      if !@object.respond_to?(method)
        raise NotImplementedError, "#{@object.class} must implement the method #{method}"
      end
    end
  end
  def ensure_asking_for_defined_method!(method)
    unless REQUIRED_METHODS.include?(method)
      raise NotDefinedMethod, "#{method} doesn't belong to Interface definition"
    end
  end
end

Então você pode escrever um objeto com pelo menos o contrato de Interface:

class FooImplementation
  def foo
    puts('foo')
  end
  def bar
    puts('bar')
  end
end

Você pode chamar seu objeto com segurança através de sua interface para garantir que você é exatamente o que a interface define

#  > FooInterface.new(FooImplementation.new).foo
# => foo

#  > FooInterface.new(FooImplementation.new).bar
# => FooInterface::NotDefinedMethod: bar doesn't belong to Interface definition

E você também pode garantir que seu objeto implemente todas as suas definições de métodos de interface

class BadFooImplementation
end

#  > FooInterface.new(BadFooImplementation.new)
# => NotImplementedError: BadFooImplementation must implement the method foo

2

Estendi um pouco a resposta de carlosayam para minhas necessidades adicionais. Isso adiciona alguns reforços e opções adicionais à classe Interface: required_variablee optional_variableque oferece suporte a um valor padrão.

Não tenho certeza se você gostaria de usar esta meta programação com algo muito grande.

Como outras respostas declararam, é melhor você escrever testes que reforcem adequadamente o que você está procurando, especialmente quando você deseja começar a aplicar parâmetros e valores de retorno.

Advertir este método apenas lança um erro na chamada do código. Os testes ainda seriam necessários para a aplicação adequada antes do tempo de execução.

Exemplo de Código

interface.rb

module Interface
  def method(name)
    define_method(name) do
      raise "Interface method #{name} not implemented"
    end
  end

  def required_variable(name)
    define_method(name) do
      sub_class_var = instance_variable_get("@#{name}")
      throw "@#{name} must be defined" unless sub_class_var
      sub_class_var
    end
  end

  def optional_variable(name, default)
    define_method(name) do
      instance_variable_get("@#{name}") || default
    end
  end
end

plugin.rb

Eu usei a biblioteca singleton para o padrão fornecido que estou utilizando. Desta forma, quaisquer subclasses herdam a biblioteca singleton ao implementar esta "interface".

require 'singleton'

class Plugin
  include Singleton

  class << self
    extend Interface

    required_variable(:name)
    required_variable(:description)
    optional_variable(:safe, false)
    optional_variable(:dependencies, [])

    method :run
  end
end

my_plugin.rb

Para minhas necessidades, isso requer que a classe que implementa a "interface" a subclasse.

class MyPlugin < Plugin

  @name = 'My Plugin'
  @description = 'I am a plugin'
  @safe = true

  def self.run
    puts 'Do Stuff™'
  end
end

2

O próprio Ruby não tem equivalente exato a interfaces em Java.

No entanto, como essa interface às vezes pode ser muito útil, desenvolvi uma gema para Ruby, que emula interfaces Java de uma maneira muito simples.

É chamado class_interface.

Funciona de forma bastante simples. Primeiro instale a gema gem install class_interfaceou adicione-a ao seu Gemfile e rund bundle install.

Definindo uma interface:

require 'class_interface'

class IExample
  MIN_AGE = Integer
  DEFAULT_ENV = String
  SOME_CONSTANT = nil

  def self.some_static_method
  end

  def some_instance_method
  end
end

Implementando essa interface:

class MyImplementation
  MIN_AGE = 21
  DEFAULT_ENV = 'dev' 
  SOME_CONSTANT = 'some_value'

  def specific_method
    puts "very specific"
  end

  def self.some_static_method
    puts "static method is implemented!"
  end

  def some_instance_method
    # implementation
  end

  def self.another_methods
    # implementation
  end

  implements IExample
end

Se você não implementar uma determinada constante ou método ou o número do parâmetro não corresponder, um erro correspondente será gerado antes que o programa Ruby seja executado. Você pode até determinar o tipo das constantes atribuindo um tipo na interface. Se nulo, qualquer tipo é permitido.

O método "implements" deve ser chamado na última linha de uma classe, pois essa é a posição do código onde os métodos implementados acima já estão verificados.

Mais em: https://github.com/magynhard/class_interface


0

Percebi que estava usando o padrão "Erro não implementado" demais para verificações de segurança em objetos para os quais queria um comportamento específico. Acabei escrevendo uma gema que basicamente permite usar uma interface como esta:

require 'playable' 

class Instrument 
  implements Playable
end

Instrument.new #will throw: Interface::Error::NotImplementedError: Expected Instrument to implement play for interface Playable

Ele não verifica os argumentos do método . Faz a partir da versão 0.2.0. Exemplo mais detalhado em https://github.com/bluegod/rint

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.