¿Podemos exponer interfaces en Ruby como lo hacemos en java y hacer cumplir los módulos o clases de Ruby para implementar los métodos definidos por interfaz?
Una forma es usar la herencia y method_missing para lograr lo mismo, pero ¿hay algún otro enfoque más apropiado disponible?
Respuestas:
Ruby tiene interfaces como cualquier otro idioma.
Tenga en cuenta que debe tener cuidado de no confundir el concepto de Interfaz , que es una especificación abstracta de las responsabilidades, garantías y protocolos de una unidad, con el concepto de
interface
que es una palabra clave en la programación Java, C # y VB.NET. Idiomas En Ruby, usamos el primero todo el tiempo, pero el segundo simplemente no existe.Es muy importante distinguir los dos. Lo importante es la interfaz , no el
interface
. Nointerface
te dice prácticamente nada útil. Nada demuestra esto mejor que las interfaces de marcador en Java, que son interfaces que no tienen miembros en absoluto: solo eche un vistazo ajava.io.Serializable
yjava.lang.Cloneable
; esos dosinterface
significan cosas muy diferentes, pero tienen exactamente la misma firma.Entonces, si dos
interface
s que significan cosas diferentes, tienen la misma firma, ¿ qué es exactamente lo queinterface
te garantiza?Otro buen ejemplo:
¿Cuál es la interfaz de
java.util.List<E>.add
?element
esta en la coleccion¿Y cuál de ellos aparece realmente en el
interface
? ¡Ninguna! No hay nada en elinterface
que diga que elAdd
método deba agregar nada, también podría eliminar un elemento de la colección.Esta es una implementación perfectamente válida de eso
interface
:Otro ejemplo: ¿en
java.util.Set<E>
qué lugar 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
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é conservarlos? ¿Por qué no ceñirse 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 interfaz "
interface
", la llamantype
. En un lenguaje de programación de tipo dependiente, puede, por ejemplo, expresar las propiedades de que unasort
función 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 ningún elemento más grande aparece antes de un elemento más pequeño.Entonces, en resumen: Ruby no tiene un equivalente a Java
interface
. Se hace , sin embargo, tiene un equivalente a una aplicación Java de interfaz , y es exactamente el mismo que en Java: documentación.Además, al igual que en Java, las pruebas de aceptación también se pueden utilizar para especificar interfaces .
En particular, en Ruby, la interfaz de un objeto está determinada por lo que puede hacer , no por lo
class
que es ni por lo quemodule
se mezcla. Se puede agregar cualquier objeto que tenga un<<
método. Esto es muy útil en las pruebas unitarias, donde se puede simplemente pasar en unaArray
oString
en lugar de una más complicadaLogger
, a pesar de queArray
yLogger
no compartir una explícitainterface
aparte del hecho de que ambos tienen un método llamado<<
.Otro ejemplo es
StringIO
, que implementa la misma de interfaz comoIO
y por lo tanto una gran parte de la interfaz deFile
, pero sin compartir ninguna antepasado común ademásObject
.fuente
interface
es inútil, perdiendo el sentido de su uso. Hubiera sido más fácil decir que ruby se escribe dinámicamente y que tiene un enfoque diferente en mente y hace que conceptos como IOC sean innecesarios / no deseados. Es un cambio difícil si está acostumbrado a diseñar por contrato. Algo de lo que Rails podría beneficiarse, de lo que el equipo central se dio cuenta, como puede ver en las últimas versiones.interface
posible que una palabra clave de Java no proporcione toda la información relevante, pero proporciona un lugar obvio para colocar la documentación. Escribí una clase en Ruby que implementa (suficiente) IO, pero lo hice por prueba y error y no estaba muy contento con el proceso. También he escrito varias implementaciones de una interfaz propia, pero documentar qué métodos son necesarios y qué se supone que deben hacer para que otros miembros de mi equipo puedan crear implementaciones resultó ser un desafío.interface
construcción solo es necesaria para tratar diferentes tipos como iguales en lenguajes de herencia única de tipo estático (por ejemplo, tratarLinkedHashSet
yArrayList
ambos como aCollection
), no tiene prácticamente nada que ver con Interface como muestra esta respuesta. Ruby no se escribe estáticamente, por lo que no es necesario el constructo .Pruebe los "ejemplos compartidos" de rspec:
https://www.relishapp.com/rspec/rspec-core/v/3-5/docs/example-groups/shared-examples
Escribe una especificación para su interfaz y luego coloca una línea en la especificación de cada implementador, por ejemplo.
Ejemplo completo:
Actualización : ocho años después (2020), ruby ahora tiene soporte para interfaces de tipo estático a través de sorbete. Consulte Clases e interfaces abstractas en los documentos de sorbete.
fuente
Ruby no tiene esa funcionalidad. En principio, no los necesita, ya que Ruby usa lo que se llama escritura pato .
Hay algunos enfoques que puede tomar.
Escriba implementaciones que generen excepciones; si una subclase intenta utilizar el método no implementado, fallará
Junto con lo anterior, debe escribir un código de prueba que haga cumplir sus contratos (qué otra publicación aquí llama incorrectamente a la interfaz )
Si se encuentra escribiendo métodos vacíos como el anterior todo el tiempo, escriba un módulo de ayuda que capture eso
Ahora, combine lo anterior con módulos Ruby y estará cerca de lo que desea ...
Y luego puedes hacer
Permítanme enfatizar una vez más: esto es rudimentario, ya que todo en Ruby sucede en tiempo de ejecución; no hay comprobación del tiempo de compilación. Si combina esto con las pruebas, debería poder detectar errores. Aún más, si lleva más allá lo anterior, probablemente podría escribir una interfaz que realice la verificación de la clase la primera vez que se crea un objeto de esa clase; haciendo que sus pruebas sean tan simples como llamar
MyCollection.new
... sí, exagerado :)fuente
Como todos dijeron aquí, no existe 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:
Eliminar uno de los métodos declarados en Person o cambiar su número de argumentos generará un
NotImplementedError
.fuente
No existen las interfaces a la manera de Java. Pero hay otras cosas que puedes disfrutar en ruby.
Si desea implementar algún tipo de tipos e interfaz, de modo que los objetos se puedan verificar 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 verificación de tipos en ruby está aquí .
Los enfoques 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 tener la forma ruby de la herencia múltiple, use el mecanismo
include
oextend
. Coninclude
puede incluir métodos de otra clase o módulo en un objeto. Conextend
puede agregar comportamiento a una clase, para que sus instancias tengan los métodos agregados. Sin embargo, esa fue una explicación muy corta.En mi opinión, la mejor manera de resolver la necesidad de la interfaz Java es comprender el modelo de objetos ruby (consulte las conferencias de Dave Thomas, por ejemplo). Probablemente se olvide de las interfaces Java. O tiene una aplicación excepcional en su agenda.
fuente
Como indican muchas respuestas, Ruby no tiene forma de obligar a una clase a implementar un método específico, heredando de una clase, incluido un módulo o algo similar. La razón de esto es probablemente la prevalencia de TDD en la comunidad 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 mediante simulaciones y códigos auxiliares. Pero también hay herramientas como Bogus , que permiten definir pruebas de contrato. Tales pruebas no solo definen el comportamiento de la clase "primaria", sino que también verifican que los métodos stubped existen en las clases cooperantes.
Si está realmente preocupado por las interfaces en Ruby, le recomendaría usar un marco de prueba que implemente las pruebas de contrato.
fuente
Todos los ejemplos aquí son interesantes, pero falta la validación del contrato de interfaz, quiero decir, si desea que su objeto implemente toda la definición de métodos de interfaz y solo estos, no puede. Así que te propongo un ejemplo rápido y sencillo (seguro que se puede mejorar) para asegurarte de que tienes exactamente lo que esperas tener a través de tu interfaz (el contrato).
considere su interfaz con los métodos definidos como ese
Entonces puede escribir un objeto con al menos el contrato de interfaz:
Puede llamar a su Objeto de forma segura a través de su Interfaz para asegurarse de que es exactamente lo que define la Interfaz
Y también puede asegurarse de que su objeto implemente toda la definición de sus métodos de interfaz
fuente
He extendido un poco la respuesta de carlosayam para mis necesidades adicionales. Esto agrega un par de implementaciones y opciones adicionales a la clase Interfaz:
required_variable
yoptional_variable
que admite un valor predeterminado.No estoy seguro de que desee utilizar esta metaprogramación con algo demasiado grande.
Como han indicado otras respuestas, es mejor que escriba pruebas que hagan cumplir adecuadamente lo que está buscando, especialmente una vez que desee comenzar a aplicar parámetros y valores de retorno.
Advertencia: este método solo arroja un error al llamar al código. Las pruebas seguirían siendo necesarias para una aplicación adecuada antes del tiempo de ejecución.
Ejemplo de código
interface.rb
plugin.rb
Usé la biblioteca singleton para el patrón dado que estoy utilizando. De esta forma, cualquier subclases hereda la biblioteca singleton al implementar esta "interfaz".
my_plugin.rb
Para mis necesidades, esto requiere que la clase que implementa la "interfaz" la subclasifique.
fuente
Ruby en sí no tiene un equivalente exacto a las interfaces en Java.
Sin embargo, dado que dicha interfaz a veces puede ser muy útil, yo mismo desarrollé una joya para Ruby, que emula las interfaces de Java de una manera muy simple.
Se llama
class_interface
.Funciona de forma bastante sencilla. Primero instale la gema
gem install class_interface
o agréguela a su Gemfile y rundbundle install
.Definición de una interfaz:
Implementando esa interfaz:
Si no implementa una determinada constante o método o el número de parámetro no coincide, se generará un error correspondiente antes de que se ejecute el programa Ruby. Incluso puede determinar el tipo de constantes asignando un tipo en la interfaz. Si es nulo, se permite cualquier tipo.
El método "implements" debe ser llamado en la última línea de una clase, porque esa es la posición del código donde los métodos implementados arriba ya están marcados.
Más en: https://github.com/magynhard/class_interface
fuente
Me di cuenta de que estaba usando demasiado el patrón "Error no implementado" para las comprobaciones de seguridad de objetos que deseaban un comportamiento específico. Terminé escribiendo una joya que básicamente permite usar una interfaz como esta:
No comprueba los argumentos del método. Lo hace a partir de la versión0.2.0
. Ejemplo más detallado en https://github.com/bluegod/rintfuente