interfaces ruby interface

interfaces - ¿Qué es la interfaz java equivalente en Ruby?



ruby abstract class (9)

¿Podemos exponer las interfaces en Ruby como lo hacemos en Java e imponer los módulos o clases de Ruby para implementar los métodos definidos por la interfaz?

Una forma es usar inheritance y method_missing para lograr lo mismo, pero ¿hay algún otro enfoque más apropiado disponible?


¿Podemos exponer las interfaces en Ruby como lo hacemos en Java e imponer los módulos o clases de Ruby para implementar los métodos definidos por la interfaz?

Ruby no tiene esa funcionalidad. En principio, no los necesita, ya que Ruby usa lo que se llama tipado de pato .

Hay pocos enfoques que puede tomar.

Escribir implementaciones que generan excepciones; si una subclase intenta usar el método no implementado, fallará

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

Junto con lo anterior, debe escribir un código de prueba que haga cumplir sus contratos (qué otra publicación aquí incorrectamente llama Interfaz )

Si te encuentras escribiendo métodos vacíos como todo el tiempo, escribe un módulo de ayuda que capture ese

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

Ahora, combina lo anterior con los módulos de Ruby y estarás cerca de lo que quieres ...

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

Y luego puedes hacer

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

Permítanme enfatizar una vez más: esto es un rudimentario, ya que todo en Ruby ocurre en tiempo de ejecución; no hay comprobación de tiempo de compilación. Si combina esto con las pruebas, entonces debería poder recoger los errores. Aún más, si lleva más allá lo anterior, probablemente pueda escribir una Interfaz que realice una verificación en la clase la primera vez que se crea un objeto de esa clase; hacer tus pruebas tan simples como llamar a MyCollection.new ... yeah, over the top :)


Como indican muchas respuestas, en Ruby no hay forma de forzar a una clase a implementar un método específico heredando de una clase, incluido un módulo o algo similar. La razón para eso es probablemente la prevalencia de TDD en la comunidad de Ruby, que es una forma diferente de definir la interfaz: las pruebas no solo especifican las firmas de los métodos, sino también el comportamiento. Por lo tanto, si desea implementar una clase diferente, que implemente alguna interfaz ya definida, debe asegurarse de que todas las pruebas pasen.

Por lo general, las pruebas se definen de forma aislada utilizando simulaciones y trozos. Pero también hay herramientas como Bogus , lo que permite definir pruebas contractuales. Dichas pruebas no solo definen el comportamiento de la clase "primaria", sino que también comprueban que los métodos stubbed existen en las clases cooperadoras.

Si realmente te interesan las interfaces en Ruby, te recomendaría utilizar un marco de prueba que implemente las pruebas de contrato.


Como todos dijeron aquí, no hay un sistema de interfaz para ruby. Pero a través de la introspección, puede implementarlo usted mismo con bastante facilidad. Aquí hay un ejemplo simple que se puede mejorar de muchas maneras para ayudarlo a comenzar:

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

La eliminación de uno de los métodos declarados en Person o su número de argumentos generará un NotImplementedError .


He ampliado un poco la respuesta de carlosayam para mis necesidades adicionales. Esto agrega un par de aplicaciones y opciones adicionales a la clase de interfaz: required_variable y optional_variable que admite un valor predeterminado.

No estoy seguro de que quieras usar esta meta programación con algo demasiado grande.

Como han indicado otras respuestas, es mejor que escriba pruebas que apliquen correctamente lo que está buscando, especialmente una vez que desee comenzar a aplicar parámetros y devolver valores.

Advertencia: este método solo arroja un error al invocar el código. Aún se requerirían pruebas para una aplicación adecuada antes del tiempo de ejecución.

Ejemplo 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

Usé la biblioteca singleton para el patrón dado que estoy utilizando. De esta forma, cualquier subclase heredará la biblioteca singleton al implementar esta "interfaz".

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 mis necesidades, esto requiere que la clase que implementa la "interfaz" lo subclasifique.

class MyPlugin < Plugin @name = ''My Plugin'' @description = ''I am a plugin'' @safe = true def self.run puts ''Do Stuff™'' end end


Me di cuenta de que estaba usando el patrón "Error no implementado" demasiado para las comprobaciones de seguridad en los objetos que quería un comportamiento específico. Terminó escribiendo una gema que básicamente permite usar una interfaz como esta:

require ''playable'' class Instrument implements Playable end Instrument.new #will throw: Interface::Error::NotImplementedError: Expected Instrument to implement play for interface Playable

No verifica los argumentos del método . Lo hace a partir de la versión 0.2.0 . Un ejemplo más detallado en https://github.com/bluegod/rint


No hay tales cosas como interfaces en la forma de Java. Pero hay otras cosas que puedes disfrutar en ruby.

Si desea implementar algún tipo de tipo e interfaz, para que los objetos puedan verificarse si tienen algunos métodos / mensajes que necesita de ellos, puede echar un vistazo a rubycontracts . Define un mecanismo similar a los PyProtocols . Un blog sobre comprobación de tipos en ruby ​​está here .

Los temas mencionados no son proyectos vivos, aunque el objetivo parece ser bueno al principio, parece que la mayoría de los desarrolladores de ruby ​​pueden vivir sin una estricta verificación de tipos. Pero la flexibilidad de Ruby permite implementar la verificación de tipos.

Si desea extender objetos o clases (lo mismo en ruby) mediante ciertos comportamientos o de algún modo tiene la forma ruby ​​de herencia múltiple, use el mecanismo de include o extend . Con include puede incluir métodos de otra clase o módulo en un objeto. Con extend puede agregar comportamiento a una clase, para que sus instancias tengan los métodos agregados. Sin embargo, esa fue una breve explicación.

En mi opinión, la mejor manera de resolver la necesidad de la interfaz Java es comprender el modelo de objeto ruby ​​(ver conferencias de Dave Thomas, por ejemplo). Probablemente te olvidarás de las interfaces Java. O tiene una aplicación excepcional en su horario.


Pruebe los "ejemplos compartidos" de rspec:

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

Usted escribe una especificación para su interfaz y luego pone una línea en las especificaciones de cada implementador, por ej.

it_behaves_like "my interface"

Ejemplo 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


Ruby tiene interfaces como cualquier otro idioma.

Tenga en cuenta que debe tener cuidado de no confundir el concepto de la Interfaz , que es una especificación abstracta de las responsabilidades, garantías y protocolos de una unidad con el concepto de la interface que es una palabra clave en Java, C # y VB.NET lenguajes de programación. En Ruby, usamos el primero todo el tiempo, pero este último simplemente no existe.

Es muy importante distinguir los dos. Lo importante es la interfaz , no la interface . La interface te dice nada útil. Nada lo demuestra mejor que las interfaces de marcador en Java, que son interfaces que no tienen ningún miembro: solo eche un vistazo a java.io.Serializable y java.lang.Cloneable ; esas dos interface significan cosas muy diferentes, sin embargo, tienen la misma firma exacta .

Entonces, si dos interface que significan cosas diferentes, tienen la misma firma, ¿ qué es exactamente lo que la interface te garantiza?

Otro buen ejemplo:

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

¿Cuál es la interfaz de java.util.List<E>.add ?

  • que la longitud de la colección no disminuye
  • que todos los artículos que estaban en la colección antes todavía están allí
  • ese element está en la colección

¿Y cuál de esos realmente aparece en la interface ? ¡Ninguna! No hay nada en la interface que diga que el método Add siquiera debe agregar nada, también podría eliminar un elemento de la colección.

Esta es una implementación perfectamente válida de esa interface :

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

Otro ejemplo: donde en java.util.Set<E> ¿dice realmente que es, ya sabes, un conjunto ? ¡En ninguna parte! O más precisamente, en la documentación. En inglés.

En casi todos los casos de interfaces , tanto de Java como de .NET, toda la información relevante está realmente en los documentos, no en los tipos. Entonces, si los tipos no te dicen nada interesante de todos modos, ¿por qué mantenerlos? ¿Por qué no apegarse solo a la documentación? Y eso es exactamente lo que hace Ruby.

Tenga en cuenta que hay otros idiomas en los que la interfaz se puede describir de manera significativa. Sin embargo, esos lenguajes normalmente no llaman a la construcción que describe la " interface " de la interface , sino que lo llaman type . En un lenguaje de programación de tipo dependiente, puede, por ejemplo, expresar las propiedades de que una función de sort devuelve una colección de la misma longitud que el original, que cada elemento que está en el original también está en la colección ordenada y que no es más grande el elemento aparece antes que un elemento más pequeño.

Entonces, en resumen: Ruby no tiene un equivalente a una interface Java. Sin embargo, tiene un equivalente a una interfaz Java, y es exactamente lo mismo que en Java: documentación.

Además, al igual que en Java, las pruebas de aceptación se pueden usar también para especificar interfaces .

En particular, en Ruby, la interfaz de un objeto está determinada por lo que puede hacer , no por la class que es o por el module se mezcla. Se puede agregar cualquier objeto que tenga un método << . Esto es muy útil en pruebas unitarias, donde simplemente puede pasar una Array o una String lugar de un Logger más complicado, aunque Array y Logger no comparten una interface explícita interface aparte del hecho de que ambos tienen un método llamado << .

Otro ejemplo es StringIO , que implementa la misma interfaz que IO y, por lo tanto, una gran parte de la interfaz de File , pero sin compartir ningún ancestro común además de Object .


Todos los ejemplos aquí son interesantes pero faltan la validación del contrato de la Interfaz, es decir, si desea que su objeto implemente todos los métodos de la interfaz y solo estos no puede. Así que le propongo un ejemplo sencillo y rápido (se puede mejorar con seguridad) para asegurarse de tener exactamente lo que espera tener a través de su Interfaz (el contrato).

Considera tu interfaz con los métodos definidos así

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 [email protected]_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

Luego puede escribir un objeto con al menos el contrato de interfaz:

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

Puede llamar a su Objeto de forma segura a través de su Interfaz para asegurarse de que es exactamente lo que la Interfaz define

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

Y también puede asegurarse de que su Objeto implemente toda su definición de métodos de Interfaz

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