ruby-on-rails - define_method - ruby include module in class
“The Ruby way”(mixins y reapertura de clases) vs. inyección de dependencia (5)
Al estudiar mixins vs. inyección de dependencia, a menudo escucho la frase "a la manera de Ruby". A menudo los desarrolladores dicen algo en la línea de
Ruby le permite reabrir las clases y redefinir los métodos significa que puede "insertar" fácilmente nuevas referencias en su código en el momento de la prueba.
(ver # 6 en http://weblog.jamisbuck.org/2007/7/29/net-ssh-revisited )
Pero las pruebas no son mi principal preocupación; mi preocupación es la reutilización de clase. Quiero clases que pueda reutilizar en múltiples aplicaciones de Rails de escala empresarial.
Entonces, ¿qué pasó con las clases de REUSIÓN? El uso de mixins y la reapertura de clases no parece proporcionar una forma de escribir clases de tal manera que se desacoplen de los detalles específicos de la aplicación sin un montón de trabajo adicional. Pero quizás me equivoque. Si lo estoy, ¿puede alguien proporcionar un enlace a un artículo que contenga un código de muestra que explique claramente cómo lograr esto correctamente usando mixins y reapertura de clases?
Como ejemplo, la clase Foo aquí está acoplada a la clase Logger:
class Foo
def initialize
@logger = new_logger
end
def new_logger
Logger.new
end
end
Sí, puedo volver a abrir Foo y redefinir new_logger, pero no puedo creer que esto se considere un enfoque realista y estándar para escribir clases reutilizables que puedan ser utilizadas por múltiples aplicaciones de Rails.
Artículo sobre cómo usar IoC en Ruby . Subestima el poder de IoC.
Con muestras de trabajo.
ACTUALIZAR
El enlace ya está muerto, aquí está la fuente https://github.com/alexeypetrushin/rubylang/blob/master/draft/you-underestimate-the-power-of-ioc.md
Usé ese IoC como parte de mi marco web, recreé un poco Ruby on Rails y funcionó, pero no proporcionó una ventaja significativa sobre RoR, tenía características similares. Entonces, se convirtió en una carga y la abandoné, algunos detalles http://petrush.in/blog/2011/rad-web-framework .
Bueno, solo porque podamos reabrir las clases en Ruby no significa que siempre tengamos que hacerlo, se puede pensar en la reapertura de las clases como método de último recurso. Tiene una biblioteca que hace todo lo que necesita, excepto por un método, en lugar de forzar a toda la biblioteca a parchearlo y usar su bifurcación, simplemente puede volver a abrir la clase, redefinir el método y volverá a estar en el negocio. Esto no es algo que harías a la ligera, pero tener la capacidad de hacerlo es extremadamente útil.
Dicho todo esto, en Ruby tenemos un concepto que casi siempre puede ser un buen sustituto de la inyección de dependencia: la tipificación de pato. Dado que no hay verificación de tipos, puede pasar cualquier objeto a una función y siempre que el objeto tenga los métodos que la función esperaría, todo funcionará bien.
Veamos su ejemplo: no es realmente la clase lo que conduce a la inyección de dependencia, no lo escribiría así en Java, por ejemplo, si quisiera inyectar algunas dependencias. Solo se puede inyectar a través del constructor o a través de captadores y configuradores. Así que vamos a reescribir esta clase de esa manera:
class Foo
def initialize(logger)
@logger = logger
end
end
Mucho mejor ahora podemos inyectar / pasar un registrador en nuestra clase de Foo. Agreguemos un método que usaría este registrador para demostrar:
class Foo
def initialize(logger)
@logger = logger
end
def do_stuff
@logger.info("Stuff")
end
end
En java, si quisiera crear objetos Foo
con diferentes tipos de registradores, todos esos registradores tendrían que implementar la misma interfaz en un sentido muy literal (por ejemplo public class logger implements Loggable
), o al menos ser clases secundarias. Pero en Ruby, siempre que el objeto tenga un método de info
que acepte una cadena, puedes pasarlo al constructor y Ruby sigue avanzando alegremente. Vamos a demostrar:
class Logger
def info(some_info)
end
end
class Widget
def info(some_widget_info)
end
end
class Lolcat
def info(lol_string)
end
end
Foo.new(Logger.new).do_stuff
Foo.new(Widget.new).do_stuff
Foo.new(Lolcat.new).do_stuff
Con todas las 3 de las instancias anteriores de la clase Foo
llamando al método do_stuff
, todo funcionará bien.
Como puede ver en este ejemplo, adherirse a los principios del diseño OO sigue siendo importante, pero Ruby es algo menos restrictivo sobre lo que aceptará, siempre y cuando los métodos correctos estén ahí, todo estará bien.
Dependiendo de cómo se mire, la tipificación de pato hace que la inyección de dependencia sea totalmente irrelevante o la hace más poderosa que nunca.
En realidad, cuando vine del mundo de Java a ruby world, lo primero que me interesó es cómo administran las dependencias. En ese momento estaba usando Google Guice en todos los proyectos de Java y me inspiré mucho por su diseño inteligente y su facilidad de uso. Lo primero que hice en ruby fue mi propio contenedor DI que tenía aproximadamente el mismo conjunto de características que Google Guice ( todavía está aquí en github pero está muy desactualizado).
Pero ahora, después de 2 años de trabajar con Rails / Ruby, creo que no se necesita DI aquí. El buen artículo sobre DI en ruby es http://weblog.jamisbuck.org/2008/11/9/legos-play-doh-and-programming . En realidad, es un artículo sobre por qué el autor de uno de los autores no necesita DI. Primeros contenedores DI para rubí. Definitivamente vale la pena leerlo.
Estoy totalmente de acuerdo. Los lenguajes dinámicos no son sustitutos de la inyección de dependencia. Y nada te impide escribir uno para un lenguaje dinámico. Aquí hay un marco de inyección de dependencias para Smalltalk: http://www.squeaksource.com/Seuss.html
La respuesta simple es que nada en el lenguaje Ruby le impide escribir clases reutilizables. El enfoque común para usar mezclas y reapertura de clases no necesariamente lo promueve, pero el lenguaje en realidad no impide otros enfoques. Piense en "The Ruby Way" como un subconjunto de "Las cosas que Ruby puede hacer".
Dicho esto, sé que, por lo general, es preferible imponer una decisión de diseño con construcciones de lenguaje, y que yo sepa (que le advierto que está lejos de estar completa en el tema), la DI no es actualmente un núcleo de Ruby. Sin embargo, un poco de Google me encontró algunos artículos sobre el tema, lo que me lleva a creer que se pueden encontrar bibliotecas para agregar DI a Ruby, si así lo deseas (aunque parece que reciben críticas de muchos rubistas). Los artículos informativos incluyeron http://weblog.jamisbuck.org/2008/11/9/legos-play-doh-and-programming two , y esta pregunta de SO . Espero que ayude.