在Ruby中依赖注入的一个好习惯是什么?

Eri*_*ese 7 ruby oop dependency-injection

我一直在阅读Sandi Metz的Ruby实用面向对象设计,以及许多在线讨论Ruby设计的网站.我很难完全理解的是实现依赖注入的正确方法.

互联网充斥着博客文章,解释了依赖注入是如何工作的,我认为这是一种非常局部的方式.

我明白这应该是坏的:

class ThisClass
  def initialize
    @another_class = AnotherClass.new
  end
end
Run Code Online (Sandbox Code Playgroud)

虽然这是一个解决方案:

class ThisClass
  def initialize(another_class)
    @another_class = another_class
  end
end
Run Code Online (Sandbox Code Playgroud)

我可以像这样发送AnotherClass.new:

this_class = ThisClass.new(AnotherClass.new)
Run Code Online (Sandbox Code Playgroud)

这是Sandi Metz至少推荐的方法.我不明白的是那条线应该去哪里?它必须走到某个地方,通常在这个示例中显示的是一条线,就像完全放在任何类,方法或模块之外的行,就好像我只是在IRB中手动输入它以进行测试.

这篇文章(以及其他)提出了这种不同的方法:

class ThisClass
  def another_class
    @another_class ||= AnotherClass.new
  end
end
Run Code Online (Sandbox Code Playgroud)

Jamis Buck将采取类似的方法:

class AnotherClass
end

class ThisClass
  def another_class_factory(class_name = AnotherClass)
    class_name.new
  end
end
Run Code Online (Sandbox Code Playgroud)

但是,这两个例子都保留了ThisClass中的AnotherClass名称,Sandi Metz说这是我们试图避免的主要事情之一.

那么这样做的最佳做法是什么?我是否应该在应用程序中创建一个"依赖"模块,其中包含作为每个类的对象的工厂的方法?

Dre*_*nmi 7

我很难完全理解的是实现依赖项注入的正确方法。

我认为“适当的”实现的最佳定义是遵循面向对象设计的SOLID原则。在这种情况下,主要是依赖反转原理

在这方面,这是唯一违反DIP(1)的解决方案:

class ThisClass
  def initialize(another_class)
    @another_class = another_class
  end
end
Run Code Online (Sandbox Code Playgroud)

在所有其他情况下,对ThisClass都有严格的依赖关系AnotherClass,没有它就无法运行。此外,如果我们希望AnotherClass用第三个替换,则需要修改ThisClass,这违反了开放式封闭原则

当然,在上面的示例中,命名参数和实例变量another_class并不理想,因为我们现在(也不需要知道)什么对象传递给我们,只要它响应预期的接口即可。这就是多态的美。

考虑以下示例,该示例取自DIP上的ThoughtBot视频

class Copier
  def initialize(reader, writer)
    @reader = reader
    @writer = writer
  end

  def copy
    @writer.write(@reader.read_until_eof)
  end
end
Run Code Online (Sandbox Code Playgroud)

在这里,您可以传递任何和分别响应reader和的writer对象。这样,您就可以在运行时使用不同的读写实现对来自由组合业务逻辑:read_until_eofwrite

Copier.new(KeyboardReader.new, Printer.new)
Copier.new(KeyboardReader.new, NetworkPrinter.new)
Run Code Online (Sandbox Code Playgroud)

这使我们想到了下一个问题。


它必须走到某个地方,通常在该示例中显示的是这样一条线,即完全放在任何类,方法或模块之外。

你是对的。尽管对象思维涉及使用隔离度高,分离度高且可组合的对象对域进行建模,但您仍将需要定义这些对象的交互方式,以实现任何业务逻辑。毕竟,除非我们将它们组合在一起,否则拥有可组合的对象是不好的。

通常在这里进行类比是将您的对象视为演员。您是导演,您仍然需要创建一个脚本(2),以使演员知道如何进行交互。

也就是说,您需要一个进入应用程序的入口点脚本开始的地方。这本身可能是一个对象,通常是一个抽象的对象。在命令行应用程序中,它可以是您的经典Main类,在Rails应用程序中,它可以是您的控制器。

乍一看这很奇怪,因为对象思考的重点是对具体领域对象进行建模,并且与此主题相关的所有著作都致力于这一工作,但只要记住演员-脚本的隐喻,您就会在途中。


我强烈建议您拿起《对象思维》一书。它很好地解释了面向对象设计背后的思维方式,如果不了解特定于语言的实现细节将变得毫无用处。


(1):值得注意的是,一些支持者认为将另一个类的实例存储在实例变量中是一种反模式,但是在Ruby中,这是相当惯用的。

(2):我不确定这是否通常是编程中脚本一词的起源,但也许某些历史学家可以对此有所了解。

  • 将另一个类的实例存储在实例变量中不是反模式,这很重要-将其限制为特定的实现是反模式。对象经常由其他对象组成。 (2认同)