はじめに
依存関係逆転の原則って、名前がかっこいいので言いたくなります。
ただ、理解が難しいのが難点です。
最初に見たとき、自分は「逆転」という言葉に引っ張られて、依存元と依存先が反対になることだと思っていました。しかし、そうではなかったようです。
しっくりきたのはDDDの勉強をしている時にこちらの本に書かれていた、具体的な実装に依存していたものが、抽象に依存するようになるという見方でした。
この記事では、この一点を中心に依存関係逆転の原則を整理してみます。
まずは逆転前の状態を見る
依存関係逆転の原則は、逆転後のきれいな形を見るより、先に逆転前を見るほうがわかりやすいです。
たとえば、注文処理をする OrderService が、メール通知クラス EmailNotifier を直接使っているコードを考えます。
public class EmailNotifier {
public void notify(String message) {
System.out.println("メール送信: " + message);
}
}
public class OrderService {
private EmailNotifier notifier;
public OrderService() {
this.notifier = new EmailNotifier();
}
public void placeOrder(String item) {
System.out.println(item + "を注文しました");
notifier.notify(item + "の注文が完了しました");
}
}
このコードで見たいのは、OrderService が EmailNotifier を知ってしまっていることです。
OrderService が本当に必要としているのは「通知する機能」のはずなのに、実際には「メールで通知するクラス」に依存しています。つまり、依存先が最初から具体的な実装です。
この状態だと、メールをSMSに変えたくなったときも、テストで通知処理だけ差し替えたいときも、OrderService 側に影響が出ます。上位の処理が下位の実装詳細に引っ張られてしまうわけです。
上位:ユーザーに近い抽象的な処理
下位:コンピュータに近い具体的な処理
抽象に依存させると景色が変わる
では、OrderService が必要としているものをそのまま抽象にしてみます。
ここで大事なのは、「メール送信」という具体的な手段ではなく、「通知できること」を切り出すことです。
public interface Notifier {
void notify(String message);
}
public class EmailNotifier implements Notifier {
@Override
public void notify(String message) {
System.out.println("メール送信: " + message);
}
}
public class SmsNotifier implements Notifier {
@Override
public void notify(String message) {
System.out.println("SMS送信: " + message);
}
}
public class OrderService {
private Notifier notifier;
public OrderService(Notifier notifier) {
this.notifier = notifier;
}
public void placeOrder(String item) {
System.out.println(item + "を注文しました");
notifier.notify(item + "の注文が完了しました");
}
}
こうすると、OrderService が依存している相手は EmailNotifier ではなく、Notifier です。
OrderService からすると、メールなのかSMSなのかはどうでもよくなります。必要なのは「通知できること」だけ。だから、実装の差し替えがかなり素直になります。
「逆転」の正体はここだった
自分がいちばん引っかかったのはここでした。
逆転前は、こうです。
OrderService → EmailNotifier
OrderService は、下位モジュールの具体クラスに依存しています。
逆転後は、こうなります。
OrderService → Notifier ← EmailNotifier
この形になると、OrderService が見ているのは具体クラスではなく抽象です。そして EmailNotifier のような下位モジュール側が、その抽象に合わせて実装する立場になります。
つまり、逆転したのは「処理の流れ」そのものではなく、具体に向いていた依存が、抽象に向くようになったということです。
対義語になっていることから逆転したのがわかりやすいです。
まとめ
依存関係逆転の原則を、自分は最初「矢印が逆向きになる話」だと思っていました。ですが、もっとシンプルに考えると、
具体的な実装に依存していたものを、抽象に依存させる。
ということでした。