LoginSignup
3
1

More than 1 year has passed since last update.

SOLID原則を勉強する その3~リスコフの置換原則(LSP)~

Last updated at Posted at 2021-07-31

目次

  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 の原則に違反する危険性

リスコフの置換原則(LSP)

  • 派生クラスは基底クラスと置き換えても、正常にプログラムが動作しなければならない
    • 事前条件を派生クラスで強化してはいけない
      • 事前条件 = ガード節 = メソッドを失敗させずに実行するために必要な条件
    • 事後条件を派生クラスで緩くしてはいけない
      • 事後条件 = メソッド終了時に処理結果が有効化チェックするための条件
    • アクセス修飾子を上書きしてはいけない

  • C# の new キーワードはこのLSP原則を破る可能性が高い
    • メソッドの上書きは virtualoverrideを使う

LSP違反するとどうなるか?

  • 間違った使い方をしてもコンパイルエラーにならなくなる(型安全性が壊れる)
    • 実際にプログラムを動作させないと分からない
    • 「この派生クラスだったら…」という条件分岐ができる

コード例

長方形クラス Rectangle と正方形クラス Square を作ります。
(Unity 感はないですが、LSP違反の例として有名らしいので)

正方形クラスは長方形クラスを継承するべきかという問題です。
「正方形 is a 長方形」と言い換えると、正しいように思えるが……

before

  • 長方形クラス Rectangle を継承して、正方形クラス Squareを作る
    • 「正方形 is a 長方形」
  • User クラス は長方形クラスを使って、辺の長さを定義して面積を計算する
Rectangle.cs
public class Rectangle
{
    public virtual int Height { get; set; }
    public virtual int Width { get; set; }

    public int Area()
    {
        return Height * Width;
    }
}
Square.cs
public class Square : Rectangle
{
    private int _height;
    private int _width;
    public override int Height
    {
        get
        {
            return _height;
        }
        set
        {
            _height = value;
            _width = value;
        }
    }
    public override int Width
    {
        get
        {
            return _width;
        }
        set
        {
            _width = value;
            _height = value;
        }
    }
}
User.cs
using UnityEngine;

public class User : MonoBehaviour
{
    void Start()
    {
        Rectangle rectangle = new Rectangle();
        rectangle.Width = 5;
        rectangle.Height = 2;
        Debug.Assert(rectangle.Area() == 10);
    }
}

ダメなところ

  • Rectangleクラスを Square クラスに置き換えできない
    • SquareRectangle のサブクラスなのに…
    • 置き換えたら意図しない結果が返ってくる
      • User クラスの new Rectangle()new Square() にするだけで Assert に引っかかるようになる
User.cs
using UnityEngine;

public class User : MonoBehaviour
{
    void Start()
    {
        //  new Square(); に変更したら Assert に引っかかるようになる。しかもコンパイルエラーにならない。
        Rectangle rectangle = new Square(); 
        rectangle.Width = 5;
        rectangle.Height = 2;
        Debug.Assert(rectangle.Area() == 10);
    }
}

after

  • 共通の基底となるインターフェイスまたは抽象クラスを作成する( IShape インターフェイス)
    • Square クラスは Rectangle クラスを継承しない
    • Square クラスと Rectangle クラスは IShape インターフェイスを継承する
IShape.cs
public interface IShape
{
    int Area();
}
Rectangle.cs
public class Rectangle : IShape
{
    public int Height { get; set; }
    public int Width { get; set; }

    public int Area()
    {
        return Height * Width;
    }
}
Square.cs
public class Square : IShape
{
    public int SideLength { get; set; }

    public int Area()
    {
        return SideLength * SideLength;
    }
}
User.cs
using UnityEngine;

public class User : MonoBehaviour
{
    void Start()
    {
        var rectangle = new Rectangle();
        rectangle.Width = 5;
        rectangle.Height = 2;
        Debug.Assert(rectangle.Area() == 10);
    }
}

良いところ

  • new Rectangle()new Square() に変更したらコンパイルエラーになるので、使い方を間違っていてもすぐに問題に気づける

終わりに

現実の世界では「正方形 is a 長方形」は正しいけど、「ソフトウェアの世界」という立場に変えたら、正しくないことが分かりました。

もし変なところがあったら教えて下さい。
(特にクラス図とか…)

3
1
3

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