Dependency Schminjection

What is DI?

class Google
  def search(query)
    http = HTTP.new('google.com')
    http.get(query)
  end
end
class GoogleTest < Test::Unit
  def test_search
    google = Google.new
    result = google.search('doge')
    assert_equal(result, '<!doctype html><html..')
  end
end

ruby

class Google
  def initialize(http)
    @http = http
  end

  def search(query)
    @http.get(query)
  end
end
class GoogleTest
  def test_search
    http = mock(http, get: 'many results. wow.')
    google = Google.new(http)

    assert_equal(google.search('doge'), 'many results. wow.')
  end
end

ruby

... that's it!

How do I initialize all this junk!?

class Container
  def google
    Google.new(http)
  end
class Container
  def google
    Google.new(http)
  end

  def http
    HTTP.new(google_url)
  end




class Container
  def google
    Google.new(http)
  end

  def http
    HTTP.new(google_url)
  end

  def google_url
    read_from_cfg("google.url")
  end


class Container
  def google
    Google.new(http)
  end

  def http
    HTTP.new(google_url)
  end

  def google_url
    read_from_cfg("google.url")
  end

  def cli
    Cli.new(google)
  end
end
class Container
end

ruby

#!/bin/ruby
# exe/google_cli.rb

require 'google'
require 'cli'

container = Container.new
container.cli.start(ARGV)

ruby

Not bad

So simple!

class Google
  def initialize(http)
    @http = http
  end

  def search(query)
    @http.get(query)
  end
end
class Container
  def google
    Google.new(http)
  end
end

ruby

New problem

class Result
  def initialize(html)
    @html = html
  end
end

ruby

class Google
  def initialize(http)
    @http = http
  end

  def search(query)
    @http.get(query)
  end
end
class Google
  def initialize(http)
    @results = []
    @http = http
  end

  def search(query)
    @http.get(query)
  end
end
class Google
  def initialize(http)
    @results = []
    @http = http
  end

  def search(query)
    html = @http.get(query)
    for item in html
      @results << Result.new(item)
    end
  end
end

ruby

class Container
  def result(html)
    Result.new(html)
  end

  # ...
class Container
  def result(html)
    Result.new(html)
  end

  def google
    Google.new(http, self)
  end

  # ...

ruby

class Google
  def initialize(http)
    @results = []
    @http = http
  end

  def search(query)
    html = @http.get(query)
    for item in html
      @results << Result.new(item)
    end
  end
end
class Google
  def initialize(http, container)
    @container = container
    @results = []
    @http = http
  end

  def search(query)
    html = @http.get(query)
    for item in html
      @results << Result.new(item)
    end
  end
end
class Google
  def initialize(http, container)
    @container = container
    @results = []
    @http = http
  end

  def search(query)
    html = @http.get(query)
    for item in html
      @results << @container.result(item)
    end
  end
end

ruby

Why not?

  • Responsibility unclear
  • Big interface
  • Reference cycles
  • Hidden complexity

Reference cycles

Hidden complexity

class Bad
  def initialize(container)
    @container = container
  end

  def save_data
    @container.db_connection.save(report)
  end

  def report
    @container.report(data)
  end

  def data
    @container.parser.parse(remote_json)
  end

  def remote_json
    @container.http.get(some_json_url)
  end
end
class Okay
  def initialize(db_connection, report_factory, json_parser, http)
    @db_connection  = db_connection
    @report_factory = report_factory
    @json_parser    = json_parser
    @http           = http
  end

  def save_data
    @db_connection.save(report)
  end

  def report
    @report_factory.report(data)
  end

  def data
    @json_parser.parse(remote_json)
  end

  def remote_json
    @http.get(some_json_url)
  end
end

ruby

A better way

class Google
  def initialize(http, container)
    @container = container
    @results = []
    @http = http
  end

  def search(query)
    html = @http.get(query)
    for item in html
      @results << @container.result(item)
    end
  end
end
class ResultFactory
  def result(html)
    Result.new(html)
  end
end

Factories!

ruby

class Container
  def result_factory
    ResultFactory.new
  end

  def google
    Google.new(http, result_factory)
  end

  # ...

ruby

class Google
  def initialize(http, factory)
    @factory = factory
    @results = []
    @http = http
  end

  def search(query)
    html = @http.get(query)
    for item in html
      @results << @factory.result(item)
    end
  end
end

ruby

Constructor

vs

Property

class ThingUsingConstructor
  def initialize(http, formatter)
    @http = http
    @formatter = formatter
  end

  def do_stuff
    resp = @http.get(url)
    @formatter.format(resp)
  end
end
class ThingUsingProperty
  attr_accessor :http, :formatter

  def do_stuff
    resp = @http.get(url)
    @formatter.format(resp)
  end
end

ruby

class ThingUsingPropertyTest < Test::Unit
  def setup
    thing = ThingUsingProperty.new
  end

  def test_do_stuff
    thing.do_stuff
  end
end
NoMethodError: undefined method `get' for nil:NilClass
	from (irb):6
	from /usr/bin/irb:12:in `<main>'

ruby

class ThingUsingConstructorTest < Test::Unit
  def setup
    thing = ThingUsingConstructor.new
  end
end
ArgumentError: wrong number of arguments (0 for 2)
	from (irb):2:in `initialize'
	from (irb):5:in `new'
	from (irb):5
	from /usr/bin/irb:12:in `<main>'

ruby

Default Properties

class MyViewController: UIViewController {
  var apiClient = ApiClient()
}

swift

class MyViewController: UIViewController {
  var apiClient = ApiClient()

  func getRoot() {
    apiClient.get('/')
  }
}

class MyViewControllerTest {
  func testGetRoot() {
    var controller = MyViewContrller()
    controller.getRoot()
  }
}

swift

Do I actually need to do DI in ___________ ?

ruby

javascript

python

class Google
  def search(query)
    http.get(query)
  end

  def http
    HTTP.new('google.com')
  end
end

class GoogleTest < Test::Unit
  def test_search
    google = Google.new

    mock_http = mock()
    mock_http.stub(:get).and_return('')
    google.stub(:http).and_return(mock_http)

    assert_equal(google.search(''), '')
  end
end
class Google
  def search(query)
    http.get(query)
  end

  def http
    HTTP.new('google.com')
  end
end

class GoogleTest < Test::Unit
  def test_search
    google = Google.new





    assert_equal(google.search(''), '')
  end
end

ruby

make it happen

make it perfect

Frameworks

Blindside

@implementation MyViewController

/**
 * Describing MyViewController's dependencies.
 */
+ (BSInitializer *)bsInitializer {
        return [BSInitializer initializerWithClass:self
                                          selector:@selector(initWithApi:)
                                      argumentKeys:@"myApi", nil];
}

Objective-C

Interface Builder

class MyViewController: UIViewController {
  @IBOutlet var label: UILabel!
}

swift

Typhoon

class MyViewController: UIViewController {
  var client: ApiClient!
}

Swinject

Home made

???

swift

Yes.