【Unity】公式プロジェクトから、デザインパターンを学んでみよう(Part 3:リスコフの置換原則)

前：https://qiita.com/kiku09020/items/bfcf01511dc385c797ac



# ●はじめに
今回は、公式プロジェクトのコードを見ながら、引き続きSOLID原則について個人的にまとめてみます。




# ●リスコフの置換原則(LiskovSubstitution)とは
**親クラスができることは子クラスにもできるようにしろ**ってことです。
クソ適当な説明なので、実際に見てもらった方が理解できます。多分




# ●プロジェクト内容
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2419795/8f92868c-92a4-9731-56aa-a41b90ffff06.png)

1. Unused/Vehicle
1. Car
1. IMovable
1. ITurnable
1. Navigator
1. RailVehicle
1. RoadVehicle
1. Train





# ●コード内容
## ○ Unused/Vehicle
未使用の「乗り物」クラスです。
**乗り物にもいろんな乗り物がありますが、全ての乗り物の動作の関数とかを記載してしまっている**感じです。
例えば、**前進、後退、右左折、という各操作の関数があるとしたら、右左折できない電車などの乗り物は、不要な関数がある状態**なので、**リスコフの置換原則に反した設計**になります。

<details><summary><b>クリックしてコードを展開</b></summary>

```.cs
using UnityEngine;

namespace DesignPatterns.LSP
{
    public class Vehicle
    {
        public float speed = 100;
        public string name;
        public Vector3 direction;

        // Don't include these in the base class; use composition instead of inheritance.
        // We put the functionality instead into the ITurnable and IMovable interfaces.

        // The RailVehicle and RoadVehicle classes can then implement only what they need.

        //public void GoForward(){}
        //public void Reverse(){}

        //public void TurnRight(){}
        //public void TurnLeft(){}
    }
}


```

</details>

コメントを翻訳してみます
>これらを基本クラスに含めないでください。継承の代わりに構成を使用します。
 代わりに、ITurnable および IMovable インターフェイスに機能を配置します。
 RailVehicle クラスと RoadVehicle クラスは、必要なものだけを実装できます。

「**インターフェース使って、RailとRoadに必要な関数だけ取り入れろ**」って感じのこと言ってます。
これは下で説明します。






## ○ IMovable
移動可能な乗り物につけるインターフェースです。
**乗り物だったら、ほとんど移動できるので、RailVehicleとRoadVehicleの両方についてます。**

<details><summary><b>クリックしてコードを展開</b></summary>

```.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace DesignPatterns.LSP
{
    public interface IMovable
    {
        public float MoveSpeed { get; set; }
        public float Acceleration { get; set; }

        public void GoForward();
        public void Reverse();

    }
}

```

</details>

プロパティは、移動に関する、移動速度や加速度などが用意されています。
また、乗り物の「移動可能」というのは、「前進・後退ができる」ということなので、**前進と後退それぞれの関数が用意されています。** (多分左右にも移動できる乗り物がありますが…そこらへんは割愛で。)



## ○ ITurnable
右左折可能な乗り物につけるインターフェースです。
**線路を通るRailVehicleは右左折できないので、RoadVehicleのみにつけます**

<details><summary><b>クリックしてコードを展開</b></summary>

```.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace DesignPatterns.LSP
{
    public interface ITurnable 
    {
        public void TurnRight();
        public void TurnLeft();

    }
}

```

</details>

右折、左折する関数が定義されています。それだけです。



## ○ RoadVehicle
道路を通る乗り物のクラスです。
**道路を通るので、前進、後退、右左折ができます。**

<details><summary><b>クリックしてコードを展開</b></summary>

```.cs
using UnityEngine;

namespace DesignPatterns.LSP
{
    public class RoadVehicle : IMovable, ITurnable
    {
        public string Name;

        private float moveSpeed = 100f;
        private float acceleration = 5f;

        public float TurnSpeed = 5f;

        public float MoveSpeed { get => moveSpeed; set => moveSpeed = value; }
        public float Acceleration { get => acceleration; set => acceleration = value; }

        public virtual void GoForward()
        {
            
        }

        public virtual void Reverse()
        {
            
        }

        public virtual void TurnLeft()
        {
            
        }

        public virtual void TurnRight()
        {
            
        }
    }
}

```

</details>

```.cs
    public class RoadVehicle : IMovable, ITurnable
```

こんな感じで移動と右左折のインターフェースを適用してます。
インターフェースは、抽象クラスの上位互換的なやつだと思っていいと思います。
複数のクラスを継承できちゃうような感じです。

気難しいことは↓で解説されてるのでどうぞ
[インターフェース - C# によるプログラミング入門](https://ufcpp.net/study/csharp/oo_interface.html)



## ○ RailVehicle
線路を通る乗り物のクラスです。
**線路を通るので、前進と後退のみしかできません。**
**右左折関数を除いているので、リスコフの置換原則に則った設計ができています。**

<details><summary><b>クリックしてコードを展開</b></summary>

```.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace DesignPatterns.LSP
{
    public class RailVehicle : IMovable
    {

        public string Name;

        private float moveSpeed = 100f;
        private float acceleration = 5f;

        public float TurnSpeed = 5f;

        public float MoveSpeed { get => moveSpeed; set => moveSpeed = value; }
        public float Acceleration { get => acceleration; set => acceleration = value; }


        // implement these differently than RoadVehicles
        public virtual void GoForward()
        {
            
        }

        public virtual void Reverse()
        {
           
        }
    }
}

```

</details>

```.cs
    public class RailVehicle : IMovable
```
今回は、移動だけできればいいのでIMovableのみ適用してます。


## ○ Car
RoadVehicleクラスの子クラスのひとつです。中身は空っぽなので省略

## ○ Train
RailVehicleのクラスの子クラスのひとつです。Car同様に省略





## ○ Navigator
運転手のクラスだと思います。
乗る乗り物によって、関数の内容を変更しています。

<details><summary><b>クリックしてコードを展開</b></summary>

```.cs

namespace DesignPatterns.LSP
{
    public class Navigator
    {
        // Imagine a turn-based game where you move the Vehicle along a prescribed path: forward, left, right

        // This example violates Liskov Substitution Principle. We cannot substitute the subtype Train
        // for the type Vehicle here.  A train cannot functionally turn left/right:

        //public void Move(Vehicle vehicle)
        //{
        //    vehicle.GoForward();
        //    vehicle.TurnLeft();
        //    vehicle.TurnRight();
        //}

        // Instead, we can use these methods that do follow Liskov Substitution principle.
        // Here, you can use a Car or a Truck wherever we use the type
        // RoadVehicle. You can use a Train where we encounter RailVehicle.

        // Enforce inheritance hierarchy based on software design, not on real-world analogies.

        public void MoveRoadVehicle(RoadVehicle roadVehicle)
        {
            roadVehicle.GoForward();
            roadVehicle.TurnLeft();
            roadVehicle.TurnRight();
        }

        public void MoveRailVehicle(RailVehicle railVehicle)
        {
            railVehicle.GoForward();
            railVehicle.GoForward();
            railVehicle.Reverse();
        }

    }
}

```

</details>

コメントの翻訳

悪例
>所定の経路 (前方、左、右) に沿ってビークルを移動するターン制のゲームを想像してください。
 この例はリスコフの置換原則に違反しています。サブタイプ Train を代用することはできません
タイプ Vehicle の場合はこちら。列車は機能的に左右に曲がることができません:

```.cs
        public void Move(Vehicle vehicle)
        {
             vehicle.GoForward();
             vehicle.TurnLeft();
             vehicle.TurnRight();
        }
```

列車は曲がれないのに、TurnLeftとTurnRightがあったらダメでしょ❗😡😡😡って言ってます。


適例
> 代わりに、リスコフの置換原理に従うこれらのメソッドを使用できます。
  ここでは、タイプを使用する場所で Car または Truck を使用できます
  RoadVehicle. RailVehicle に遭遇した場所で Train を使用できます。
  (現実世界の類推ではなく、ソフトウェア設計に基づいて継承階層を適用します。)

```.cs
        public void MoveRoadVehicle(RoadVehicle roadVehicle)
        {
            roadVehicle.GoForward();
            roadVehicle.TurnLeft();
            roadVehicle.TurnRight();
        }

        public void MoveRailVehicle(RailVehicle railVehicle)
        {
            railVehicle.GoForward();
            railVehicle.GoForward();
            railVehicle.Reverse();
        }
```

ガバ翻訳ですが…「道路を移動する乗り物」と「線路を移動する乗り物」で分けて、別々の処理にしろよ❗✊😁って言ってます。


# ●さいごに
公式のドキュメントからの引用ですが、今回の設計を図式化するとこんな感じです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2419795/bff3362a-1a87-1950-d5a0-4d22dadbefac.png)

矢印は、そのクラスが参照しているクラスへの方向を示しています。
こういうのをクラス図っていうらしいです。すごい見やすい…。少しめんどくさそうですが、いつか作ってみます。

インターフェース、使えたらすごい便利だなーと思いました。もっと早く知りたかった！！！

次：https://qiita.com/kiku09020/items/a7452be8fc6663304264