使用Ruby替换运行时实现

Jam*_*ory 6 ruby dependency-injection

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

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

class A
end

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

  def new_client
    @client_impl.new
  end
end
Run Code Online (Sandbox Code Playgroud)

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

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

if (IsServerAvailable)
  container.Register<IChatServer>(new CenteralizedChatServer());
else
  container.Register<IChatServer>(new DistributedChatServer());
Run Code Online (Sandbox Code Playgroud)

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

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

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

Yeh*_*atz 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
Run Code Online (Sandbox Code Playgroud)

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

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)
Run Code Online (Sandbox Code Playgroud)

你也可以混合搭配技巧:

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
Run Code Online (Sandbox Code Playgroud)

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