Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Runtime implementation substitution with Ruby

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.

like image 236
James Gregory Avatar asked Jul 04 '09 07:07

James Gregory


1 Answers

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.

like image 64
Yehuda Katz Avatar answered Nov 14 '22 02:11

Yehuda Katz