はじめに
Unity公式がプログラミングパターンのデモを説明書と共に全12種公開していたので、1つずつ読み取っていきたいと思います!
英語の説明書でしたので間違った解釈をしているかもしれませんが、ご了承下さい!
最初の5種は、SOLID原則に沿ったデモとなっており、コードのみが公開されています。
残り7種は、Scene付きのデモが入っていますので、ZIPファイルをダウンロードして試してみてください。
今回は、3つ目のLiskovSubstitution(リスコフの置換原則)についてです。
環境
OS : Windows10
Unity バージョン : 2021.3.8f1
LiskovSubstitution(リスコフの置換原則)とは
LiskovSubstitution(リスコフの置換原則)は、派生クラスは基底クラスと置き換え可能でなければならないという原則です。
オブジェクト指向プログラミングでは、継承により派生クラスに機能を追加できますが、注意をしないと不必要に複雑になってしまいます。
デモでは乗り物を例にしたコードが掲載されていました。
悪いコード
基底クラスをVehicleクラスとして、派生クラスに車やトラックを作成するとなると以下のようになると思います。
public class Vehicle
{
public float speed = 100;
public Vector3 direction;
public void GoForward()
{
}
public void Reverse()
{
}
public void TurnRight()
{
}
public void TurnLeft()
{
}
}
しかし、派生クラスに電車を追加したくなったときはどうでしょうか?
電車は、前後に進むことはできますが、方向を変えることはできません。
この設計を修正するには、元のVehicleクラスの機能をインターフェースに移動させます。
良いコード
public interface ITurnable
{
public void TurnRight();
public void TurnLeft();
}
public interface IMovable
{
public void GoForward();
public void Reverse();
}
これでVehicleクラスの機能が2つの前後の動きと曲がる機能のインターフェースに分かれました。
これらのインターフェースは、2つの基底クラスに割り振られます。
public class RoadVehicle : IMovable, ITurnable
{
public float speed = 100f;
public float turnSpeed = 5f;
public virtual void GoForward()
{
}
public virtual void Reverse()
{
}
public virtual void TurnLeft()
{
}
public virtual void TurnRight()
{
}
}
public class RailVehicle : IMovable
{
public float speed = 100;
public virtual void GoForward()
{
}
public virtual void Reverse()
{
}
}
前後左右の動きができるRoadVehicleクラスと前後の動きのみのRailVehicleクラスに分けることができました。
これで、車と電車は同じ基底クラスを共有することなく以下のように実装できます。
public class Car : RoadVehicle
{
}
public class Train : RailVehicle
{
}
リスコフの置換原則を守るためのヒント
リスコフの置換原則を守るためには、以下4点を念頭に置いておくと良いらしいです。
- 派生クラスで基底クラスの機能を削除していないか
派生クラスで基底クラスの機能を削除している場合、リスコフの置換原則に違反している可能性があります。メソッドを空白にしていても同様に違反している可能性があります。 - 抽象化はシンプルになっているか
基底クラスに追加するロジックが多いほどリスコフの置換原則を守りにくくなります。 - 派生クラスは基底クラスと同じpublicメンバを持っているか
publicメンバは呼び出し時に同じシグネチャと動作も必要です。 - クラス階層を確立する前にクラスAPIを検討する
車と電車は乗り物で考えることができますが、別々の基底クラスを継承する方が理にかなっている場合があります。 - 継承よりも構成を優先する
継承によって機能を渡すのではなく、インターフェースまたは別クラスを作成して、特定の動作をカプセル化することを優先させてください。
参考