LoginSignup
11
8

More than 1 year has passed since last update.

SOLID原則を勉強する その5~依存性逆転の原則(DIP)~

Last updated at Posted at 2021-08-17

目次

  1. SOLID原則を勉強する その1~単一責任の原則 (SRP)~
  2. SOLID原則を勉強する その2~オープン・クローズド原則(OCP)~
  3. SOLID原則を勉強する その3~リスコフの置換原則(LSP)~
  4. SOLID原則を勉強する その4~インターフェース分離の原則(ISP)~
  5. SOLID原則を勉強する その5~依存性逆転の原則(DIP)~ ←いまここ

前置き

書籍を読んだり、ググったりして、自分に分かりやすいようにまとめた記事です。
より詳しく知りたい方は、下記の参考文献を読んでみてください。

参考文献

Clean Architecture 達人に学ぶソフトウェアの構造と設計 | Amazon
Adaptive Code ~ C#実践開発手法 | Amazon
C#の設計の基本【SOLID原則】まとめ
Unity開発で使える設計の話+Zenjectの紹介
C# で SOLID の原則に違反する危険性

依存性逆転の原則(DIP)

  • 上位モジュールが下位モジュールに依存してはいけない
    • 上位モジュールも下位モジュールも抽象に依存すべき
      • 上記モジュール = 相手を使う側のクラス
      • 下位モジュール = 上位から使われるクラス
    • 抽象は詳細に依存してはならない
      • 詳細が抽象に依存すべき

  • 依存性注入(Dependency injection)にも話が繋がる
    • 依存性注入 = オブジェクトを外から注入する
      • コンストラクタで外からインスタンスを受け取る
      • クラス内ではインターフェースしか操作しない
    • Unityだと Zenject という依存性注入を管理してくれるフレームワークがある
    • 依存性注入と依存性逆転の原則とは別の話なので注意

  • 変化しやすい具象クラスを参照しない
    • 抽象クラス、インターフェースを参照するべき
    • Abstract Factory パターンを使う
  • 変化しやすい具象クラスを継承しない
    • 継承は一種の依存関係なので、気をつけて使うことが賢明
  • 具象関数をオーバーライドしない
    • 具象関数はソースコードの依存関係を要求することが多い = 依存関係を排除できない

コード例

Unity開発で使える設計の話+Zenjectの紹介で紹介されているコードを参考にしていますが、クラス名などを変更しています。

before

SlashAttack.cs
using UnityEngine;

namespace Before.DIP.Attack
{
    public class SlashAttack
    {
        public void ApplyDamage()
        {
            Debug.Log("斬撃攻撃でダメージを与える");
        }
    }
}
Player.cs
using Before.DIP.Attack;

namespace Before.DIP.Player
{
    public class Player
    {
        public void Attack()
        {
            var attack = new SlashAttack();
            attack.ApplyDamage();
        }
    }
}

ダメなところ

  • 下位モジュールである Attack パッケージが変更されると、上位モジュールの Player の変更が必要になる
    • シグネチャ(メソッド名、引数)が変更されたら、 Player を変更しなければいけない
    • Attack の種類が増えたら、処理の分岐を追加しなければいけない

After

  • インターフェースを Player 側に定義する
    • そのインターフェースを使うことで、矢印の向きを逆にする
    • Player は下位モジュールの Attack を知らない
    • SlashAttackPlayer 側で定義したインターフェースを実装する
Player.cs
namespace After.DIP.Player
{
    public class Player
    {
        IAttack _attack;
        public Player(IAttack attack)
        {
            _attack = attack;
        }

        public void Attack()
        {
            _attack.ApplyDamage();
        }
    }
}
IAttack.cs
namespace After.DIP.Player
{
    public interface IAttack
    {
        void ApplyDamage();
    }
}
SlashAttack.cs
using After.DIP.Player;
using UnityEngine;

namespace After.DIP.Attack
{
    public class SlashAttack : IAttack
    {
        public void ApplyDamage()
        {
            Debug.Log("斬撃攻撃でダメージを与える");
        }
    }
}

良いところ

  • SlashAttack が変更されても、 Player を変更せずにすむ
  • SlashAttack から別のクラスに置き換えるときも簡単に可能
    • コンストラクタに渡す IAttack を実装したオブジェクトを変更するだけ
    void Start()
    {
        var attack = new SlashAttack(); // ここを変えるだけ
        var player = new Player(attack);
        player.Attack();
    }

新しい攻撃 MagicAttack を追加しても。 Player に影響がない

終わりに

もし、変なところがあったらぜひ教えてください。
他の設計原則についても、いずれまとめる予定です。

11
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
8