いやいや、いきなり逆転って言われてもなんのこっちゃわからんわ
と思ったので。自分なりに調べたところを記録しておきます。
アーキテクチャについて調べているとよくお目にかかる、オブジェクト指向プログラミングの設計原則SOLID(Wikipedia)の五番目の子(D)だそうです。
逆転ってなんやねん?(翻訳:逆転とはどういう意味ですか?)
ものすごく簡単に結論からいうと、設計上望ましい依存の方向性と、素直に実装しようとしたときの方向性は矛盾しちゃうので、そこをテクニックでカバーして逆転させると、じつはスッキリと望ましい設計通りに実装できますよ!という人類の知恵です。1
素直に実装したときの依存の方向と逆。
処理の流れは、ソースコードの依存性とは逆向きになることに注意しよう。ソースコードの依存性と処理の流れは逆向きになる。だからこそ「依存関係逆転の原則(DIP)」と名付けたのである。2
ソースコードの依存性と処理の流れが逆。
逆転の雰囲気については、なんとなくわかったような・・・?
どうやんねん?(翻訳:どのように実現すればよいのですか?具体的に教えてください。)
上位のモジュールは下位のモジュールに依存してはならない。どちらのモジュールも、抽象に依存すべきである。
抽象は、実装の詳細に依存すべきではない。実装の詳細が、抽象に依存すべきである。
実践ドメイン駆動設計 p.118
3
実装の詳細が、抽象に依存すべき。
例として、MVPパターンでPresenterとViewを考えます。
素直に実装すると、Presenterが以下のようにViewに依存しています。どうやらこれが依存関係逆転の原則に違反しているようです。
class Presenter {
let view: View
required init(view: View) {
self.view = view
}
func updateText(_ text: String) {
view.setText(text)
}
}
class View: UIView {
let label: UILabel
...
func setText(_ text: String) {
label.text = text
}
}
これを抽象を使って、依存性を逆転させます。
つまり、Presenter側で定義した抽象にViewという具象を依存させます。
// ViewProtocolという抽象を定義
protocol ViewProtocol: UIView {
func setText(_ text: String)
}
class Presenter {
// Presenterも同じく抽象に依存
let view: ViewProtocol
required init(view: ViewProtocol) {
self.view = view
}
func updateText(_ text: String) {
view.setText(text)
}
}
// Viewを抽象に依存させる ← 逆転!
class View: UIView, ViewProtocol {
let label: UILabel
...
func setText(_ text: String) {
label.text = text
}
}
具体的なコードで見てみると、「ソースコードの依存性と処理の流れが逆」という意味がわかりますね。
Presenterは、実行時には具体的に実装されたViewのインスタンスを通じてメソッドを呼び出す(処理の流れ)けれど、ソースコード上では逆向きに依存してるよね、というお話です。
なにが嬉しいん?(翻訳:こうすることでどんな利益が得られるのですか?)
ソフトウェアは「ソフト」になるように考案された……振る舞いを簡単に変更する手段になることを目的としたもの……簡単に変更したくないときは、それを「ハード」ウェアと呼んだ。4
ソフトウェアは振る舞いを簡単に変更できなければいけない。
依存関係というものがあります。プログラミングの世界では、たとえば、AというモジュールがBというモジュールを読み込む場合、AはBに依存しているといいます。
このとき、Bを変更するとAにも影響が生じます。このときの当然のことながらBだけの問題ではなく、Aについても影響範囲の調査や改修が必要になります。1
依存関係があると、変更のコストが大きくなる。
安定依存の原則 (SDP: Stable Dependencies Principle)
設計原則の一つに「安定依存の原則」があります。「依存の方向は、より安定した方向に向かわなければならない」というものです。実装を進めているとモジュール間の依存は必ず発生します。その方向を安定したモジュールに向かうようにすることで、ソフトウェア全体の修正頻度・修正範囲が低く小さく抑えられるというものです。安定依存の原則に従えば、ソフトウェアの安定性を高めることができます。5
依存する方向を安定したモジュールに向けるほど、変更に関わるコストが小さくなる。
抽象インターフェイスの変更は、それに対応する具象実装の変更につながる。一方、具象実装を変更してもインターフェイスの変更が必要になることはあまりない。つまり、インターフェイスは実装よりも変化しにくいということだ。
優れたソフトウェア設計者やアーキテクトは、インターフェイスの変動性をできるだけ抑えようとする。新しい機能を実装するときにも、できる限りインターフェイスの変更なしで済ませられるようにする。これは、ソフトウェア設計の基本中の基本だ。
安定したソフトウェアアーキテクチャは、変化しやすい具象への依存を避け、安定した抽象インターフェイスに依存すべきである。2
具象実装に比べて安定した抽象を依存先とすることで、安定したソフトウェアアーキテクチャに近く。
まとめると、簡単に変更できるようなソフトウェアを作るためには、依存先をできるだけ安定した(変更の少ない)対象にするほうが良いよ。
依存関係逆転の原則は、抽象を依存先にするという方法で、それに貢献しますよ、ということですね。
ほな、さいなら(翻訳:おわりに)
自分なりには上記のように理解したつもりですが、ほかの本や記事を読んでいるといろんな文脈で使われていて、もう少し違う解釈もあるのかなと思ったりします。
ご意見・ご感想お待ちしております!