Dependency Injection frameworks in Ruby have been pretty much declared unnecessary. Jamis Buck wrote about this last year in his LEGOs, Play-Doh, and Programming blog post.
The general accepted alternative seems to be using some degree of constructor injection, but simply supplying defaults.
class A
end
class B
def initialize(options={})
@client_impl = options[:client] || A
end
def new_client
@client_impl.new
end
end
This approach is fine by me, but it seems to lack one thing from more traditional setups: a way of substituting implementations at runtime based on some external switch.
For example with a Dependency Injection framework I could do something like this (pesudo-C#):
if (IsServerAvailable)
container.Register<IChatServer>(new CenteralizedChatServer());
else
container.Register<IChatServer>(new DistributedChatServer());
This example just registers a different IChatServer
implementation depending on whether our centeralized server is available.
As we're still just using the constructor in Ruby, we don't have programatic control over the dependencies that are used (unless we specify each one ourselves). The examples Jamis gives seem well suited to making classes more testable, but seem to lack the facilities for substitution.
What my question is, is how do you solve this situation in Ruby? I'm open to any answers, including "you just don't need to do that". I just want to know the Ruby perspective on these matters.
In addition to constructor substitution, you could store the information in an instance variable ("attribute"). From your example:
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
You could also allow the dependency to be made available as an option to the method:
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)
You could also mix-and-match techniques:
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
Basically, when people talk about Ruby and DI, what they mean is that the language itself is flexible enough to make it possible to implement any number of styles of DI without needing a special framework.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With