4
3

More than 3 years have passed since last update.

SOLID原則を勉強する その2 ~オープン・クローズド原則(OCP)~

Last updated at Posted at 2021-07-23

目次

  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の紹介

オープン・クローズド原則(OCP)

  • 拡張に対して開いている(オープン)
    • 機能追加は簡単にできる
    • 新しい機能を追加するとき、既存のコードにフックして新しいふるまいを提供できるようにする(拡張ポイント)
      • 仮想メソッド( virtual
      • 抽象クラス・メソッド( abstract
      • インターフェースの継承( interface
      • sealed を指定して継承を禁止する(指定されていないクラスは継承してもよいと判断できる)
  • 変更に対して閉じている(クローズ)
    • 機能を追加するとき、修正が発生してはいけない
      • モジュールのソースやバイナリコードの変更が発生しない
    • クライアント側を変更しない場合は例外
      • 密結合 = あるクラスを変更したら別クラスを変更しなければならない状態
      • 疎結合 = あるクラスを変更しても別クラスを変更する必要がない状態
      • 疎結合を維持する = 「変更に対して閉じている」

コード例

弾が敵に当たったらダメージを与えるクラスを作ります。
Unity開発で使える設計の話+Zenjectの紹介 で紹介されているコードを参考にしています。

before

Bullet.cs
using UnityEngine;

public class Bullet : MonoBehaviour
{
    void OnCollisionEnter(Collision collision)
    {
        var hit = collision.gameObject;
        var enemy = hit.GetComponent<Enemy>();

        if (enemy == null)
            return;

        switch (hit.tag)
        {
            case "Shadow":
                enemy.ApplyDamageToShadow();
                break;
            case "Gigas":
                enemy.ApplyDamageToGigas();
                break;
        }
    }
}
Enemy.cs
using UnityEngine;

public class Enemy : MonoBehaviour
{
    public void ApplyDamageToShadow()
    {
        Debug.Log("Shadow");
    }

    public void ApplyDamageToGigas()
    {
        Debug.Log("Gigas");
    }
}

ダメなところ

  • もし敵が増えたらコード修正が必要
    • OnCollisionEnter を変更しなければならない
      • 機能追加なのに修正が発生してしまう
    • Enemy クラスにも関数を追加する必要がある
      • Enemy クラスが肥大化、何でもできる神クラスになっちゃう → 単一責任の原則的に良くない
    • 他にも似た switch 文がないか探さないといけない

例)ダークインフェルトという敵を新しく実装することがあった

Bullet.cs
using UnityEngine;

public class Bullet : MonoBehaviour
{
    void OnCollisionEnter(Collision collision)
    {
        var hit = collision.gameObject;
        var enemy = hit.GetComponent<Enemy>();

        if (enemy == null)
            return;

        switch (hit.tag)
        {
            case "Shadow":
                enemy.ApplyDamageToShadow();
                break;
            case "Gigas":
                enemy.ApplyDamageToGigas();
                break;
            case "DarkInferno":
                enemy.ApplyDamageToDarkInferno();
                break;
        }
    }
}
Enemy.cs
using UnityEngine;

public class Enemy : MonoBehaviour
{
    public void ApplyDamageToShadow()
    {
        Debug.Log("Shadow");
    }

    public void ApplyDamageToGigas()
    {
        Debug.Log("Gigas");
    }

    public void ApplyDamageToDarkInferno()
    {
        Debug.Log("DarkInferno");
    }
}

after

  • 拡張ポイントとして、 インターフェースを使って処理を呼び出す実装に変更
    • IEnemy インターフェースを用意
    • 敵を追加するときは、 IEmeny インターフェースを実装して、ダメージを与える関数 ApplyDamage() を実装
Bullet.cs
using UnityEngine;

public class Bullet : MonoBehaviour
{
    void OnCollisionEnter(Collision collision)
    {
        var hit = collision.gameObject;
        var enemy = hit.GetComponent<IEnemy>();

        if (enemy == null)
            return;

        enemy.ApplyDamage();
    }
}
IEnemy.cs
public interface IEnemy
{
    void ApplyDamage();
}
Shadow.cs
using UnityEngine;

public class Shadow : MonoBehaviour, IEnemy
{
    public void ApplyDamage()
    {
        Debug.Log("Shadow");
    }
}
Gigas.cs
using UnityEngine;

public class Gigas : MonoBehaviour, IEnemy
{
    public void ApplyDamage()
    {
        Debug.Log("Gigas");
    }
}
DarkInferno.cs
using UnityEngine;

public class DarkInferno : MonoBehaviour, IEnemy
{
    public void ApplyDamage()
    {
        Debug.Log("DarkInferno");
    }
}

終わりに

Enemy の名前の元ネタは、キングダムハーツです。
もし、変なところがあったらぜひ教えてください。

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