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

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

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

我明白这应该是坏的:

class ThisClass
  def initialize
    @another_class = AnotherClass.new
  end
end

虽然这是一个解决方案:

class ThisClass
  def initialize(another_class)
    @another_class = another_class
  end
end

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

this_class = ThisClass.new(AnotherClass.new)

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

This post(以及其他)提出了这种不同的方法

class ThisClass
  def another_class
    @another_class ||= AnotherClass.new
  end
end

Jamis Buck将采取类似的方法

class AnotherClass
end

class ThisClass
  def another_class_factory(class_name = AnotherClass)
    class_name.new
  end
end

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

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

解决方法

Something I’ve had a hard time fully understanding is the proper way to implement dependency injection.

我认为“正确”实现的最佳定义是遵循面向对象设计的SOLID principles.在这种情况下,主要是依赖性倒置原则.

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

class ThisClass
  def initialize(another_class)
    @another_class = another_class
  end
end

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

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

请考虑以下示例,取自此ThoughtBot video on DIP

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

  def copy
    @writer.write(@reader.read_until_eof)
  end
end

在这里,您可以传递任何响应read_until_eof和write的读写器对象.这使您可以完全自由地使用不同的读写实现组合构建业务逻辑,即使在运行时也是如此:

copier.new(KeyboardReader.new,Printer.new)
copier.new(KeyboardReader.new,NetworkPrinter.new)

这将我们带到您的下一个问题.

It has to go somewhere and generally in examples of this what’s shown is a line like that being placed totally outside of any class,method,or module […]

你是对的.虽然对象思维涉及使用隔离良好,解耦和可组合的对象对域进行建模,但您仍需要定义这些对象如何交互,以实现任何业务逻辑.毕竟,除非我们编写它们,否则使用可组合对象并不好.

这里经常做的类比是将你的对象想象为演员.你是导演,你仍然需要为演员创建一个脚本(2),让他们知道如何互相交流.

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

这一开始可能看起来很奇怪,因为对象思维的重点是建模具体的领域对象,关于这个主题的大量所有着作都致力于这项工作,但只记得演员 – 脚本的比喻,你将成为在路上.

我强烈建议你拿起Object Thinking这本书.它很好地解释了面向对象设计背后的思维方式,没有它,知道语言特定的实现细节变得相当徒劳.

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

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

相关文章

validates:conclusion,:presence=>true,:inclusion=>{...
一、redis集群搭建redis3.0以前,提供了Sentinel工具来监控各...
分享一下我老师大神的人工智能教程。零基础!通俗易懂!风趣...
上一篇博文 ruby传参之引用类型 里边定义了一个方法名 mo...
一编程与编程语言 什么是编程语言? 能够被计算机所识别的表...
Ruby类和对象Ruby是一种完美的面向对象编程语言。面向对象编...