【SOLID】依存性逆転原則~大事なのは、抽象にもとづくこと

dependency-inversion-principle
依存関係逆転原則

Dependency Inversion Principle

上位モジュールは下位モジュールに依存してはならない。両者は抽象に依存すべき。

抽象は詳細に依存してはならない。詳細は抽象に依存すべき。

ソフトウェアの構造を強固にする「レイヤー」の概念。これを支える重要原則です。

SOLID原則と呼ばれる、主要5原則のひとつ。

SOLID原則
目次

どんなときに使える?

共通モジュールに振り回されるとき

コモンさん

データ送信メソッド、引数追加しましたー

コモンさん

ファイル出力メソッド、戻り値修正しましたー

リーダーさん

もう、呼び出してるとこ、また修正しないといけない

モジュールには、使う側と、使われる側があります。使う側が使われる側に依存している。その時の関係がどうあるべきか、という話です。

上位モジュールと下位モジュールの関係

上位モジュールは使う側、下位モジュールは使われる側

例として、RPGゲームでのセーブで考えます。

ゲームコントローラクラス。ゲームの流れをコントロールするクラスです。

ゲームコントローラー
ゲームコントローラクラス

ゲームをいろいろコントロールします

アクセスコントロールクラス。ゲーム進行状況のセーブを行うクラスです。

アクセスコントロール
アクセスコントロールクラス

ゲームの進行状況をセーブします

ゲームコントローラクラスは、アクセスコントロールクラスを呼んでセーブします、このとき、

  • 呼ぶ側(ゲームコントローラ):上位モジュール
  • 呼ばれる側(アクセスコントロール):下位モジュール

と呼びます。

モジュールの関係
モジュールの関係

上位モジュールは下位モジュールに依存する

ゲームコントローラクラスは、アクセスコントロールクラスでセーブします。なのでアクセスコントロールクラスは必要なわけです。

この、相手がないと動かない状態を、依存している、といいます。

上位モジュールは、下位モジュールがないと動かないので、上位モジュールが下位モジュールに依存している、という状態です。

インターフェースをどちらの都合にあわせるか

セーブする、を考えるとき、上位モジュールと下位モジュールの間のインターフェース(なにをやり取りするか)を決める必要があります。

上位モジュールのやりたいことは、とてもシンプル。

上位モジュールがやりたいこと
ゲームコントローラクラス

セーブがしたい

対して下位モジュールは、いろいろ細かいことをやる必要あります。

下位モジュールがやること
アクセスコントロールクラス

○○フォルダの△△ファイルに□□形式でセーブする

どちらの都合に合わせましょうか?

上位モジュールが、下位モジュールに合わせるとします。下位モジュールに必要な情報を渡します。

下位モジュールに依存した呼び出し
ゲームコントローラクラス

○○フォルダの、△△ファイルに、□□形式で、セーブする

アクセスコントロールクラス

○○フォルダの△△ファイルに□□フォーマット形式でセーブします

これをいちいちしていると、たいへんなことになります。

上位モジュールは、ほかにもいろいろ下位モジュールを使うはず。下位モジュールにいちいち合わせてインターフェースを決めていたら、きりがないわけです。

インタフェースは抽象にもとづいて決める

この両者の都合は、それぞれ「抽象」「詳細」と言い換えることができます。

  • 抽象 ほんとうにやりたいこと。「セーブする」
  • 詳細 実際やらないといけないこと。「○○フォルダの△△ファイルに□□フォーマット形式でセーブする」

そして

抽象は、変わりにくい。セーブしたいという要求は、変わらないでしょう。

詳細は、変わりやすい。フォルダの場所やファイル名や形式は、実装にもとづき見直すことが多いでしょう。

であれば、

詳細にインターフェースを合わせるのでなく、ほんとうにやりたいこと=抽象にもとづいて、インターフェースを決めるそうすると、変更の影響を受けにくくなります

上位モジュールに依存した呼び出し
ゲームコントローラクラス

セーブがしたい

アクセスコントロールクラス

セーブします
(○○フォルダの△△ファイルに□□フォーマット形式でセーブしよう)

上位モジュールは、自分のやりたいことに注力できます。

下位モジュールは、抽象にもとづいたインターフェースに従い、自分自身で実際やらないといけないことを判断します。

両者は、抽象に依存する

新たな登場人物「インターフェース」

インターフェースは、抽象に基づき決めたほうが、変更の影響を受けにくい。

でも、相変わらず、上位モジュールが下位モジュールに依存している、のは変わりません。

ここで、「インターフェース」と名乗る登場人物を、新たに登場させます。

インターフェース
インターフェース

セーブします(でも自分ではやりません)

アクセスコントロールクラスの代わりとなる人です。セーブというインターフェースだけ持っているが、実際なにもしない

そうすると、

  • 上位モジュールは、「インターフェース」に「セーブする」をお願いします。本当は誰がやっているか分かりません
  • 下位モジュールは、「インターフェース」に「セーブ」の依頼が来たら、代わりにセーブ作業します。本当は誰からお願いされたか分かりません。
モジュールの依存関係
モジュールの依存関係

こうすると、上位モジュール、下位モジュール、それぞれが「インターフェース」、つまり抽象に依存している、という状態となります。

上位モジュールと下位モジュールは、直接の依存関係がなくなります

Javaでの実装イメージ。AccessControlInterface が、自分ではなにもしない「インターフェース」。

GameControllerとAccessControlのいずれも、他方を直接使っていません。

import java.util.*;

// インターフェース
interface AccessControlInterface {
    public void save();
}

// アクセスコントロール(下位モジュール)
class AccessControl implements AccessControlInterface {
    // インターフェースsaveを実装
    public void save() {
        System.out.println("save");
    }
}

// ゲームコントローラ(上位モジュール)
class GameController {
    // セーブする
    public void save(AccessControlInterface ac) {
        // インターフェースに対してセーブを要求
        ac.save();
    }
}

public class Main {
    public static void main(String[] args) throws Exception {
        GameController gc = new GameController();
        AccessControl ac = new AccessControl();
        
        gc.save(ac);
    }
}

依存関係の逆転

さらに、「インターフェース」つまり抽象の部分を、上位パッケージに含めるとします。

依存性の逆転
依存性の逆転

上位モジュールが、ほんとうにやりたいことを基準としてインタフェースを決めます。下位モジュールは、インターフェースに従った実際の作業を行います。

これで依存の向きは、下位パッケージから上位パッケージの方向に変わります依存関係が逆転しました。安定する方向に依存するようになるわけです。

現実にはどうするか?

新人くん

じゃあ、下位モジュールの分だけ、インターフェースを全部作ればいいんですね!

リーダーさん

それは、やりすぎ!

依存関係が逆転できるからといって、下位モジュールのインターフェースをなんでもかんでも用意する…

というのも、現実的にはあまり好ましくありません。

このインターフェースのしかけは、上位モジュールがどの下位モジュールを呼んでいるのか直接分からなくなります。なので処理が追いづらくなるという欠点があるのです。

今後、詳細の変更が見込まれるような場合に限定し、ほどほどに使うのがよいでしょう。

まとめ

依存関係逆転の登場は、下位モジュールに依存するのが当然といった考えを覆す、驚きの考えでした。

ただそれにこだわりすぎると、逆に分かりにくいプログラムとなってしまいます。

大事なのは、この考えです。

インターフェースで大事なこと

ほんとうにやりたいこと=抽象にもとづいて、インターフェースを決めること

この考えでインターフェースを決めたのであれば、無理に仕掛けに拘る必要はないでしょう。

SOLID原則
よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

CAPTCHA


目次