【C#設計】SOLID原則をUnity公式サンプルで学ぼう~O:開放閉鎖の原則~

はじめに

講座トップに戻る

この講座ではプログラミングの設計を勉強する際に避けては通れない「SOLID原則」について学ぶことができます。

SOLID原則は有名ですので名前を知っている人も多いかもしれませんが、全然知らない人でも大丈夫です!

初心者でも分かりやすいようにできるだけ丁寧に解説します。

「Unityの使い方は分かってきたけど、コードが綺麗に書く方法が分からない」

「設計を勉強するといいらしいけど、どうやって勉強したらいいか分からない」

こんな悩みを持っているあなたは、この記事で解決できるかもしれません!

この記事ではUnity公式のサンプルをもとに解説していきます。

公式サンプルのダウンロード方法はこちらの記事を参考にしてください。

SOLID原則とは?

OLID原則とはオブジェクト指向において、拡張性・保守性が高く保つために守る原則のことです。

これは、ソフトウェアエンジニアのRobert C. Martinが提唱した多くの設計原則を5つにまとめたものです。

それぞれの頭文字をとってSOLID原則と呼ばれています。

  • S … Single Responsibility Principle: 単一責任の原則
  • O … Open-Closed Principle: 開放閉鎖の原則
  • L … Liskov Substitution Principle: リスコフの置換原則
  • I … Interface Segregation Principle: インターフェイス分離の原則
  • D … Dependency Inversion Principle: 依存性逆転の原則

何も考えずにプログラムを書いていると、段々と拡張性・保守性が下がってきます。

  • 機能の追加、変更に時間がかかる
  • コードに再利用性がない
  • 膨大な機能を持ったクラスが出来上がる(こういうクラスを神クラスと言ったりします笑)
  • 依存関係が複雑でクラス同士の関係性がハッキリしていない

SOLID原則を学ぶことで、上記のような悩みを解決できる可能性があります。

今回はSOLID原則のO「開放閉鎖の原則」を解説していきます。

SOLID原則などの設計に関する学習をしていると「モジュール」という言葉が良く出てきます。

モジュールというと分かりにくいかもですが、C#の場合クラスに置き換えて考えてください。

開放閉鎖の原則とは?

これは「クラスが拡張に対しては開いており、修正に対しては閉じていなければならない」という原則です。

拡張に対して開いている状態とは?

機能を追加(拡張)する場合は既存のコードを変更する必要がない状態を「拡張に対して開いている」といいます。

新キャラを追加する場合を考えます。

もしも「すべてのキャラを1つのクラスで管理しており、ある部分にコードを追加する」という作りだったとします。

キャラが増えるたびに同じクラスを編集することになるので、いつの日かミスをしそうですよね。

この場合、新しいキャラ用のクラスを作るだけで新しいキャラ追加できるのが望ましい形になります。

修正に対して閉じている状態とは?


機能を変更(修正)する場合は他のコードに影響が出ない状態を「修正に対して閉じている」といいます。

このような作りにしておくと「修正することによって既存の機能にバグが生まれてしまった」という悲しい事件が減ります。

Unity公式サンプルを見てみよう

Assets/2 OpenClosed/Scriptsというフォルダの中に4つのスクリプトがあります。

これらのスクリプトはそれぞれ以下のような内容になっています。

  • AreaCalculator.cs・・・面積を計算するためのクラス
  • Circle.cs・・・円のクラス
  • Rectangle.cs・・・矩形(長方形)のクラス
  • Shape.cs・・・形状ごとの基底クラス

図形の面積を求めるプログラムがサンプルになっています。

良くない例がコメントアウトして書かれているのでまずはそちらを見ていきましょう。

改善前のコード

・矩形クラス

public class Rectangle
{
    public float Width { get; set; }
    public float Height { get; set; }
}

・円クラス


public class Circle
{
    public float Radius { get; set; }
}

・面積計算クラス


public class AreaCalculator
{
    //矩形の面積を求める関数
    public float GetRectangleArea(Rectangle rectangle)
    {
        return rectangle.Width * rectangle.Height;
    }
    //円の面積を求める関数
    public float GetCircleArea(Circle circle)
    {
        return circle.Radius * circle.Radius * Mathf.PI;
    }
}

現状の悪い点

現状のコードでは図形ごとに面積を求める関数が分かれています。

これでは図形を追加するごとにAreaCalculatorの関数を増やさないといけません。

これは「拡張に対して開いている」状態で「開放閉鎖の原則」の開放部分に反します。

また各図形クラスを変更した際にAreaCalculatorで使っている部分に変更が出た際はAreaCalculator側も修正が必要です。

この状態は「修正に対して閉じている」状態で「開放閉鎖の原則」の閉鎖部分に反します。

改善後のコード

・形状クラス

public abstract class Shape
{
    //面積計算関数
    public abstract float CalculateArea();
}

・矩形クラス


public class Rectangle : Shape
{
    public float Width { get; set; }
    public float Height { get; set; }
    //矩形の面積計算
    public override float CalculateArea()
    {
        return Width * Height;
    }
}

・円クラス

public class Circle
{
    public float Radius { get; set; }
    //円の面積計算
    public override float CalculateArea()
    {
        return Radius * Radius * Mathf.PI;
    }
}

・面積計算クラス


public class AreaCalculator
{
    //面積を取得
    public float GetArea(Shape shape)
    {
        return shape.CalculateArea();
    }
}

改善後の良い点

形状クラス(Shape)が追加されて、Rectangle・CircleクラスはShapeクラスを継承しています。

Shapeクラスには面積を計算するCalculateArea関数がabstractで定義されているので継承先は必ず実装しなければなりません。

これによってAreaCalculatorクラスは各自が持つCalculateArea関数呼び出すだけで面積が取得できるようになりました。

この変更で以下の点が改善されました。

  • 図形を増やす際はShapeクラスを継承したクラスを新しく作ればいいだけ
  • 各図形に変更が入ってもAreaCalculatorはCalculateArea関数を呼び出すだけなので修正する必要がない

これで「開放閉鎖の原則」に従った作りになりました。

まとめ

以上が「開放閉鎖の原則」でした。

この原則を守ると変更箇所や修正箇所が減るので、開発がかなり楽になります。

原則自体は知らなくても自然とやっていた方もいるのではないでしょうか?

できていなかった人はこれから少しずつ意識していきましょう!

👇その他の原則を学ぶ

S:単一責任の原則

O:開放閉鎖の原則←今ここ

L:リスコフの置換原則

I:インターフェイス分離の原則

D:依存性逆転の原則

👇あなたにお勧めの書籍

デザインパターンが分かりやすくまとめられていて、設計を学ぶ際におすすめです!

コメント