使用Ruby替换运行时实现

时间:2009-07-04 07:11:38

标签: ruby dependency-injection

Ruby中的依赖注入框架几乎被宣布为不必要的。 Jamis Buck去年在他的LEGOs, Play-Doh, and Programming博客文章中写到了这一点。

普遍接受的替代方案似乎是使用某种程度的构造函数注入,但只是提供默认值。

class A
end

class B
  def initialize(options={})
    @client_impl = options[:client] || A
  end

  def new_client
    @client_impl.new
  end
end

这种方法对我来说很好,但似乎缺少传统设置中的一件事:一种在运行时基于某些外部开关替换实现的方法。

例如,使用依赖注入框架,我可以做这样的事情(pesudo-C#):

if (IsServerAvailable)
  container.Register<IChatServer>(new CenteralizedChatServer());
else
  container.Register<IChatServer>(new DistributedChatServer());

此示例仅根据我们的中心服务器是否可用来注册不同的IChatServer实现。

由于我们仍然只是在Ruby中使用构造函数,因此我们没有对所使用的依赖项进行编程控制(除非我们自己指定每个依赖项)。 Jamis提供的示例似乎非常适合使类更易于测试,但似乎缺乏替换设施。

我的问题是,你是如何在Ruby中解决这种情况的?我愿意接受任何答案,包括“你根本不需要这样做”。我只想知道Ruby对这些问题的看法。

1 个答案:

答案 0 :(得分:8)

除了构造函数替换之外,您还可以将信息存储在实例变量(“attribute”)中。从你的例子:

class A
end

class B
  attr_accessor :client_impl

  def connect
    @connection = @client_impl.new.connect
  end
end

b = B.new
b.client_impl = Twitterbot
b.connect

您还可以允许依赖项作为方法的选项提供:

class A
end

class B
  def connect(impl = nil)
    impl ||= Twitterbot
    @connection = impl.new.connect
  end
end

b = B.new
b.connect

b = B.new
b.connect(Facebookbot)

你也可以使用混合搭配技术:

class A
end

class B
  attr_accessor :impl

  def initialize(impl = nil)
    @impl = impl || Twitterbot
  end

  def connect(impl = @impl)
    @connection = impl.new.connect
  end
end

b = B.new
b.connect # Will use Twitterbot

b = B.new(Facebookbot)
b.connect # Will use Facebookbot

b = B.new
b.impl = Facebookbot
b.connect # Will use Facebookbot

b = B.new
b.connect(Facebookbot) # Will use Facebookbot

基本上,当人们谈论Ruby和DI时,他们的意思是语言本身足够灵活,可以在不需要特殊框架的情况下实现任意数量的DI风格。