Dependency Injection 依存オブジェクトの注入 についてのおぼえがき

Dependency Injectionって「依存性の注入」じゃないの?

やっていることは、 依存しているオブジェクトを注入するようにする なので、「依存オブジェクトの注入」の方が訳としてただしいのでは? という議論がある

little-hands.hatenablog.com

あと、自分が読んでいるオブジェクト指向設計実践ガイドでも「依存オブジェクトの注入」が採用されているので、こちらを使う

依存オブジェクトの注入 ってどういう時の使うの?

依存性が高いオブジェクトを内包している時。そのオブジェクトを外部から注入するように修正する

具体的にはこんなコード(オブジェクト指向設計実践ガイドより)

class Gear
  attr_reader :chainring, :cog, :rim, :tire
  def initialize(chainring, cog, rim, tire)
    @chainring = chainring
    @cog = cog
    @rim = rim
    @tire = tire
  end

  def gear_inches
    ratio * Wheel.new(rim, tire).diameter
  end
  # ...
end
Gear.new(52, 11, 26, 1.5).gear_inches

このコードのどこが問題なの?

ギアのインチを計算する Gear#gear_inches が、Wheel依存した状態 である

結果的に以下のような問題が発生する

  • 変更時のコストが高い
    • 例えば Wheel.new のためだけに、rimtireGear#initialize に渡されている。そのため、Wheel.new の引数が変わったら、Gear は大幅に変更が必要になる
    • そもそもここに Wheel があることを知らなければ、修正が必要なことにも気が付けない
  • 再利用性が低い
    • Wheel 以外のオブジェクトからギアを計算したい時、別のメソッドが必要
  • テスト実装のコストが高い
    • テストを書く時に、Wheel オブジェクトのことを考慮する必要がある

じゃあどんなふうに直したら良いの?

こんなふうに、Gear に対して外部から、diameter を呼べるオブジェクトを注入してあげれば良い

class Gear
  attr_reader :chainring, :cog, :wheel
  def initialize(chainring, cog, wheel)
    @chainring = chainring
    @cog = cog
    @wheel = wheel
  end

  def gear_inches
    ratio * wheel.diameter
  end
  # ...
end
Gear.new(52, 11, Wheel.new(26, 1.5)).gear_inches

こうすれば、 Gear は、Wheel に依存しなくなり、 wheel については #diameter さえ実行できればどんなオブジェクトでも良くなる → ダックタイピング

なので、Wheel が変更されてね影響は少なくなるし、テストするときも簡単な mock を作れば良いだけになる

蛇足

むしろ、「依存性オブジェクトの排除」とか言ったほうが意味が通じそう、という話を会社のQiita::Teamでした