LoginSignup
1
1

More than 1 year has passed since last update.

【Unity】公式プロジェクトから、デザインパターンを学んでみよう(Part 5:依存性逆転の原則)

Last updated at Posted at 2022-11-01

●はじめに

前:https://qiita.com/kiku09020/items/a7452be8fc6663304264

今回は、SOLID原則の依存性逆転の原則についてを個人的にわかりやすくまとめてみます

●依存性逆転の原則とは

依存関係逆転の原則 (DIP) では、高レベル モジュールは低レベル モジュールから直接何もインポートしてはならないと言われています。 どちらも抽象化に依存する必要があります。

(Unity公式ドキュメントから引用)

上位モジュールが下位モジュールに依存せず、どちらも抽象化に依存しろっていう原則」っぽいです
何言ってるか分かんないですね。

上位モジュールは、下位モジュールを利用して動作するクラスとかのこと
下位モジュールは、上位モジュールを動作させるために必要なクラスとかのこと
抽象化は、インターフェースのこと
らしい。

以下で実際に見ながら学んでみます。

●プロジェクト内容

image.png

  1. Door
  2. ISwitchable
  3. Switch
  4. Trap

また、公式のドキュメントを見てみると…

上位モジュールは、キャラクターを特定の場所に移動させて、何かを起こさせたいと考えています。 スイッチはそれを担当します。
下位モジュールは、ドア ジオメトリを開く方法の実際の実装を含む別のクラス Door があります。

翻訳がガバってたので、少し修正を加えましたが、
上位がSwitch、下位がDoor,Trapだそうです。
プレイヤーをスイッチ(上位モジュール)がある場所まで移動させて、それによってドア(下位モジュール)が開くっぽいです。

なので、スイッチは直接ドアやトラップなどに依存せずに、インターフェースを依存するべき、です。
インターフェースは、ISwitchableです。

図式化すると、こんな感じです。(公式ドキュメントから引用)
image.png

上位モジュールであるSwitchがISwitchableを依存(参照)して、DoorがISwicthableを継承して、動作を実装しています。
これが依存性逆転の原則です。
これによって、わざわざドアがスイッチ切り替えされた時の処理、トラップがスイッチ切り替えされた時の処理をSwitchクラスに直接記述しなくて済む感じです。
また、今回はインターフェースを利用していますが、抽象クラスでも実現可能です。(今回の例では、インターフェースの方が分かりやすいかも)

●コード内容

○Switch

スイッチに関するスクリプトです
今回はドアとかトラップが含まれているので、スイッチを押したらドアが開く、とかトラップが発動するとかっていう動作が考えられますが、そのほかに電気がつくとか、快適なエアコンがつく、とか色んな動作を切り替えることができそうです。

クリックしてコードを展開
.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace DesignPatterns.DIP
{
    public class Switch : MonoBehaviour
    {
        // old implementation:

        //public Door Door;
        //public bool IsActivated;

        //public void Activate()
        //{
        //    if (IsActivated)
        //    {
        //        IsActivated = false;
        //        Door.Close();
        //    }
        //    else
        //    {
        //        IsActivated = true;
        //        Door.Open();
        //    }
        //}

        // new implementation with ISwitchable client

        public ISwitchable client;

        public void Toggle()
        {
            if (client.IsActive)
            {
                client.Deactivate();
            }
            else
            {
                client.Activate();
            }
        }
    }
}

上部のコメントアウトされているのが、古い実装です。

.cs
public Door Door;

↑で、ドアのクラスのみを宣言してるので、他のトラップとか電気とか快適なエアコンは、別で宣言しないといけません
ドア、トラップ、電気、エアコン…スイッチによって動作が切り替わるものを追加(拡張)したくなると、いちいち宣言したり、関数を呼び出したりしないといけなくなります。これは、開放・閉鎖の原則に反しています。

逆に、下の新しい実装を見てみると、インターフェースを変数clientとして宣言してます。

.cs
public ISwitchable client;

インターフェースISwitchableを介して、SwitchとSwitchで動作をするもの( client )の動作を繋いでいます。
また、こうすることで開放・閉鎖の原則にも従うことができます。

また、Toggle()では、clientのプロパティである IsActivetrueだったら、非アクティブ化をして、falseだったらアクティブ化をする という処理を実装しています。(具体的な動作は、下のDoorの処理に記載しています。)

○ISwitchable

Switchによって、動作が切替わるクライアントにつけられるインターフェースです。
今回は、DoorやTrapについてます。

クリックしてコードを展開
.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace DesignPatterns.DIP
{
    public interface ISwitchable 
    {
        public bool IsActive { get; }

        public void Activate();
        public void Deactivate();
    }
}

プロパティには、ドアやトラップがONかどうかのフラグが宣言されています。
また、関数は、ドアがONになったときの処理と、OFFになったときの処理が宣言されています。

○Door

Switchによって動作するクライアントの1つです。
ISwicthableを介して、Swicthと繋がれています。

クリックしてコードを展開
.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace DesignPatterns.DIP
{
    // old implementation

    //public class Door : MonoBehaviour
    //{
    //    public void Open()
    //    {
    //        Debug.Log("The door is open.");
    //    }

    //    public void Close()
    //    {
    //        Debug.Log("The door is closed.");
    //    }
    //}


    // revised implementation with ISwitchable

    public class Door : MonoBehaviour, ISwitchable
    {
        private bool isActive;
        public bool IsActive => isActive;

        public void Activate()
        {
            isActive = true;
            Debug.Log("The door is open.");
        }

        public void Deactivate()
        {
            isActive = false;
            Debug.Log("The door is closed.");
        }
    }

}

古い実装では、上のSwitch内にあったように、「開閉」 について実装されています。
新しい実装では、「開閉」ではなく、ISwicthableがついてるクライアント共通の「アクティブ(ON)か非アクティブ(OFF)か」 が実装されています。

また、プロパティを変数に同期(?)させて、setもできるようにしてます。↓
外部のSwitchにはgetのみで、クライアントにはget,setもできるっぽい。

.cs
        private bool isActive;
        public bool IsActive => isActive;

Active()内では、isActivetrueに切り替えたり、開いたときの処理を簡略化した処理 が記述されています。
Deactive()内も同様に、isActivefalseにしたり閉じる処理をしています。

.cs
        public void Activate()
        {
            isActive = true;
            Debug.Log("The door is open.");
        }

        public void Deactivate()
        {
            isActive = false;
            Debug.Log("The door is closed.");
        }

これで、SwitchのToggle()が呼ばれると、isActiveがfalseだったらtrueになってドアを開く、trueだったらfalseになってドアを閉じる という処理が実行されます。

○Trap

Doorとほぼ同じなので、省略

●さいごに

どっちが上位か下位かどうかがわかんなかったですが、記事を書いててインターフェースを適用するクライアントが下位モジュール、インターフェースを依存するものが上位モジュールってのが理解できました。

ドキュメントの説明とクラス図のおかげです!!!!
よければ↓からDLしてみてください
https://resources.unity.com/games/level-up-your-code-with-game-programming-patterns

次:https://qiita.com/kiku09020/items/795448ee8607e73c393a

1
1
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
1
1