初めに
適切に設計されたオブジェクトは単一責任になっているので実際のアプリケーションでは複数のオブジェクトを組み合わせて仕事をしています。あるクラスから他のクラスを呼び出したり、モジュールをインクルードしたり。そうすることで相互に依存が発生します。依存が発生すること自体は避けられませんが、依存の方法を管理することで変更に強いコードにすることが可能です。
管理しやすいコード、変更に強いコードを書けるようになりたくてオブジェクト指向について学習しています。
学んだことをまとめます。
依存関係があるとはどんな状態か
オブジェクトが以下について知っているときは依存関係がある状態です。
- 他のクラスの名前
- self以外に送ろうとしているメッセージ(メソッド)の名前
- メッセージが要求する引数
- そのメッセージが要求する引数の順番
コード例
class Gear
def initialize
end
def gear_inches
ratio * Wheel.new(rim, tire).diameter
end
end
Gearクラスは
- Wheelというgear_inchesメソッド内でクラス名を知っている(1)
- Wheelクラスにdiameterメソッドがあることを知っている(2)
- Wheelクラスの初期化に rim, tire が必要ということを知っている(3)
- rim, tireという順でnewメソッドに渡さないといけないということを知っている(4)
正しく設計されたオブジェクトは単一責任なので依存関係は発生します。
ただ、依存の仕方を工夫することで変更に強いコードにすることができます。
依存を注入する(DIパターン)
Wheelというクラス名を知っている(1) に対応する方法としてDIパターン(dependency injection)があります。外部クラスをメソッド内で初期化して使用するのではなく、外部クラスのインスタンスを受け取ってそれを使用するという方法です。
コード例
class Gear
attr_reader :wheel
def initialize(wheel)
@wheel = wheel
end
def gear_inches
ratio * wheel.diameter
end
end
gear_inchesでWheelクラスをnewするのではなくGearクラスを初期化する際に引数として受け取るようにしました。
この変更で受けられるメリットは
- Wheelクラスのクラス名が変更されたとしても影響が及ばなくなった
- diameterというメソッドを持つクラスなら受け入れられるようになった
今まではWheelクラスのdiameterと固定されていたのが、さまざまなクラスで再利用できるようになりました。
インスタンスを受け取るようにしたので
Wheelクラスの初期化に rim, tire が必要ということを知っている(3)
rim, tireという順でnewメソッドに渡さないといけないということを知っている(4)
についてもGearクラスは知る必要がなくなって疎結合になりました。(4)の順番についてはRubyであれば引数をハッシュで渡すかキーワード引数を使うことで順番に関する知識を持つ必要がなくなります。
このような対応が制約上取れないといった場合はWheelクラスをgear_inchesで初期化するのではなく初期化用のメソッドに切り出すなどの方法をとることでGearクラスがWheelクラスに依存しているということを表現することができます。