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对这些问题的看法。
答案 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风格。