Rubyにおける依存性の注入
結論から言うとRubyでは無理に依存性を注入できるようなコードにする必要はない。
※ 依存を持つコードを書いてもよいというわけではない。
依存性の注入とは
依存していた部分を、外から注入すること
参考
一般的な依存性の注入のアプローチ
依存性を持つコード
code:ruby
class NotePresenter
def initialize
@note_storage = NoteTextStorage.new
end
def pending_notes
notes = @note_storage.get_all
notes.select { |n| n.pending? }
end
end
データはNoteTextStorageによって提供されているが、もしDBベースのデータに変更されるとしたら?
@note_storage = NoteTextStorage.newを@note_storage = NoteDatabeseStorage.newに直接このクラスNotePresenterを書き換える必要が出てくる。
依存性を注入できるコードに書き換える。
code:ruby
class NotePresenter
def initialize(storage)
@note_storage = storage
end
def pending_notes
notes = @note_storage.get_all
notes.select { |n| n.pending? }
end
end
これで、@note_storageがget_allやn.pending?という振る舞いされ持っていれば良いことになった。
NoteTextStorageクラスやNoteDatabeseStorageクラスにそういうメソッドがあることが保証されていればよい。
参考
Rubyはどこにでも依存性を注入できるよ
以下のコード
code:ruby
def publish!
self.update_attributes(created_at: Time.now)
end
これだとTimeの時間を操作することができないから時間による場合分けテストができないよね?
Timeは依存されているので外から注入できるようにする。
code:ruby
def publish!(time=Time.now)
self.update_attributes(created_at: time)
end
でも実際に使うときはtimeを渡さない。
時間は常に現在の時間を使うから。
publish!(time=Time.now)はテストのためだけに注入されている。
Rubyは以下のようにテストできるので、最初の書き方でも問題なくテストできるよ。
code:ruby
Time.stub(:now) { Time.new(2012, 12, 24) }
article.publish!
assert_equal 24, article.published_at.day
参考
But if you've acquired a design taste for the Java-friendly pattern of dependency injection, it looks gross.
DHHいわく、「JavaみたいなDIパターンはキモい」
モンキーパッチもあるしなんでもできるよ
サンプル
code:ruby
# テストのためのモンキーパッチ
class String
alias alias_empty? empty?
def empty?
true
end
end
puts ''.empty? == true
puts 'test'.empty? == true
# テストが終わったら元に戻す
class String
alias empty? alias_empty?
end
puts ''.empty? == true
puts 'test'.empty? == true
結果
code:bash
true
true
true
false
参考
ほとんどの静的言語は依存性を注入できるように書く必要があるよ
JavaやGo
参考