在研究 mixin 与依赖注入时,我经常听到“Ruby 方式”这个短语。开发人员经常会说一些类似的话
Ruby 允许您重新打开类并重新定义方法,这意味着您可以
在测试时轻松地将新引用“注入”到您的代码中。
(参见#6 http://weblog.jamisbuck.org/2007/7/29/net -ssh-revisited)
但是测试不是我主要关心的;我关心的是类的重用。我想要可以在多个企业级 Rails 应用程序中重用的类。
那么 REUSING 类发生了什么?使用 mixins 和重新打开类似乎并没有提供一种编写类的方法,使它们与特定于应用程序的细节分离,而无需进行大量额外的工作。但也许我错了。如果是的话,有人可以提供包含示例代码的文章的链接,该文章清楚地解释了如何使用 mixin 和重新打开类来正确完成此任务吗?
作为一个例子,这里的 Foo 类与 Logger 类耦合:
class Foo
def initialize
@logger = new_logger
end
def new_logger
Logger.new
end
end
是的,我可以重新打开 Foo 并重新定义 new_logger,但我只是不敢相信这被认为是编写可供多个 Rails 应用程序使用的可重用类的现实、标准方法。
In studying mixins vs. dependency injection, I often hear the phrase "the Ruby way." Often developers say something along the lines of
Ruby lets you reopen classes and redefine methods means that you can
easily "inject" new references into your code at test-time.
(see #6 at http://weblog.jamisbuck.org/2007/7/29/net-ssh-revisited)
But testing is not my main concern; my concern is class reuse. I want classes I can reuse in multiple enterprise-scale Rails applications.
So what happened to REUSING classes? Using mixins and reopening classes does not seem to provide a way to write classes in such a way that they are decoupled from application-specific details without a bunch of extra work. But Perhaps I am wrong. If I am, can someone provide a link to an article containing sample code that clearly explains how to accomplish this properly using mixins and reopening of classes?
As an example, the class Foo here is coupled to the class Logger:
class Foo
def initialize
@logger = new_logger
end
def new_logger
Logger.new
end
end
Yes, I can reopen Foo and redefine new_logger, but I just cannot believe this is considered a realistic, standard approach to writing reusable classes usable by multiple Rails applications.
发布评论
评论(5)
实际上,当我从 Java 世界来到 Ruby 世界时,我感兴趣的第一件事就是它们如何管理依赖关系。当时我在所有 java 项目中都使用 Google Guice,它巧妙的设计和易用性给我带来了很大的启发。我在 ruby 中做的第一件事是我自己的 DI 容器,它具有与 Google Guice 大致相同的功能集 - (它仍然在 github 上,但已经非常过时了)。
但现在,在使用 Rails/Ruby 两年之后,我认为这里不需要 DI。关于 ruby 中 DI 的好文章是 http://weblog .jamisbuck.org/2008/11/9/legos-play-doh-and-programming 它实际上是一篇关于为什么第一个 DI 容器的作者不需要 DI 的文章红宝石。这绝对值得一读。
Actually when I came from Java world to ruby world the first thing that I was interested in is how do they manage dependencies. At that time I was using Google Guice in all java projects and I was really inspired by its clever design and ease of use. The first thing that I've done in ruby was my very own DI container that had approximately the same set of features as Google Guice - (it is still here on github but it is very outdated).
But now after 2 years of working with Rails/Ruby I think that DI is not needed here. The good article on DI in ruby is http://weblog.jamisbuck.org/2008/11/9/legos-play-doh-and-programming it is actually an article about why DI is not needed by author of one of the first DI containers for ruby. It is definitely worth reading.
好吧,我们可以在 Ruby 中重新打开类并不意味着我们总是必须这样做,您可以将重新打开类视为最后的手段。你有一个库,除了一个方法之外,它可以完成你需要的所有事情,而不是分叉整个库修补它并使用你的分叉,你可以简单地重新打开类,重新定义方法,然后你就可以再次开始工作了。这不是你愿意做的事情,但有能力做到这一点是非常有用的。
说了这么多,在 Ruby 中,我们有一个概念几乎总是可以很好地替代依赖注入——鸭子类型。由于没有类型检查,您可以将任何对象传递给函数,只要该对象具有函数期望的方法,一切都会正常工作。
让我们看看你的例子 - 它并不是真正有利于依赖注入的类,你不会在Java中这样写,例如,如果你想注入一些依赖项。您只能通过构造函数或 getter 和 setter 进行注入。因此,让我们以这种方式重写这个类:
更好的是,我们现在可以将记录器注入/传递到我们的 Foo 类中。让我们添加一个使用此记录器来演示的方法:
在 java 中,如果您想使用不同类型的记录器创建
Foo
对象,所有这些记录器都必须在非常字面意义上实现相同的接口(例如公共类记录器实现 Loggable
),或者至少是子类。但在 Ruby 中,只要对象有一个接受字符串的info
方法,您就可以将其传递到构造函数中,Ruby 就会继续愉快地运行。让我们演示一下:通过调用
do_stuff
方法的上述Foo
类的所有 3 个实例,一切都会正常工作。从这个例子中可以看出,遵守 OO 设计原则仍然很重要,但 Ruby 对于接受的内容限制较少,只要有正确的方法,一切都会好起来的。
根据您的看法,鸭子类型要么使依赖注入完全无关,要么使其比以往更强大。
Well just because we can reopen classes in Ruby doesn't mean we always have to, you can think of reopening classes as the method of last resort. You have a library which does everything you need except for one method, rather than forking the whole library patching it and using your fork, you can simply reopen the class, redefine the method and you're in business again. This is not something you would do willy-nilly, but having the ability to do this is extremely useful.
Having said all of that, in Ruby we have a concept that can almost always be a good substitute for dependency injection - duck typing. Since there is no type checking you can pass any object into a function and as long as the object has the methods that the function would expect, everything will work fine.
Let us look at your example - it is not really class that is conducive to dependency injection, you would not write it like this in Java, for example, if you wanted to inject some dependencies. You can only inject through the constructor or through getters and setters. So let's rewrite this class in that way:
Much better we can now inject/pass in a logger into our Foo class. Let's add a method that would use this logger to demonstrate:
In java if you wanted to create
Foo
objects with different types of loggers, all those loggers would have to implement the same interface in very literal sense (e.g.public class logger implements Loggable
), or at least be child classes. But in Ruby as long as the object has aninfo
method that accepts a string, you can pass it into the constructor and Ruby keep chugging along merrily. Let's demonstrate:With all 3 of the above instances of the
Foo
class calling thedo_stuff
method, everything will work fine.As you can see from this example adhering to the principles of OO design is still important, but Ruby is somewhat less restrictive about what it will accept, as long as the right methods are there everything will be fine.
Depending on how you look at it duck typing either makes dependency injection totally irrelevant or makes it more powerful than ever.
简单的答案是,Ruby 语言中没有任何东西可以阻止您编写可重用的类。使用混合和类重新开放的常见方法并不一定会促进它,但该语言实际上并不会阻止其他方法。将“Ruby Way”视为“Ruby 可以做的事情”的子集。
也就是说,我知道通常最好使用语言构造来强制执行设计决策,并且据我所知(我会警告您在这个主题上还远未完成)DI 目前并不是核心的 Ruby 主义。不过,我在 Google 上找到了一些关于这个主题的文章,这让我相信,如果您愿意的话,可以找到一些库可以将 DI 添加到 Ruby(尽管它们似乎受到了许多 Rubyists 的批评)。内容丰富的文章包括这些 两个,和这个问题。希望有帮助。
The simple answer is that nothing in the Ruby language prevents you from writing re-usable classes. The common approach to using mix-ins and class reopening doesn't necessarily promote it, but the language doesn't actually prevent other approaches. Think of "The Ruby Way" as a subset of "The Things Ruby Can Do."
That said, I know that it's usually preferable to enforce a design decision with language constructs, and to my knowledge (which I will warn you is far from complete on the subject) DI isn't currently a core Ruby-ism. A bit of Googling found me some articles on the subject, though, leading me to believe that there are libraries to be found to add DI to Ruby, if you so desire (even though they seem to receive criticism from many Rubyists). Informative articles included these two, and this SO question. Hope that helps.
有关如何在 Ruby 中使用 IoC 的文章 您低估了IoC
带有工作示例。
更新
链接现在已失效,这是源代码https://github.com/alexeypetrushin/rubylang/blob/master/draft/you-underestimate-the-power-of-ioc.md
我使用该 IoC 作为我的Web 框架,我重新创建了 Ruby on Rails,它可以工作,但是,它不会比 RoR 具有显着的优势,具有类似的特性。所以,它变成了一种负担,我放弃了它,一些细节http://petrush.in/博客/2011/rad-web-framework 。
Article about how to use IoC in Ruby You underestimate the power of IoC
With working samples.
UPDATE
Link is dead by now, here's the source https://github.com/alexeypetrushin/rubylang/blob/master/draft/you-underestimate-the-power-of-ioc.md
I used that IoC as a part of my web framework, I kinda re-created Ruby on Rails and it worked, but, it won't give significant advantage over RoR, had similar characteristics. So, it became a burden and I abandoned it, some details http://petrush.in/blog/2011/rad-web-framework .
我完全同意。动态语言不能替代依赖注入。没有什么可以阻止你为动态语言编写一个。这是 Smalltalk 的依赖注入框架: http://www.squeaksource.com/Seuss.html
I totally agree. Dynamic languages are no substitutes for dependency injection. And nothing stops you from writing one for a dynamic language. Here's a dependency injection framework for Smalltalk: http://www.squeaksource.com/Seuss.html