Qemelly(けめる)のプログラム備忘録

Unity / AtCoderについて書きます

【Unity】SOLID原則を1から理解する - Single Responsibility編

SOLID原則なるプログラミングの大原則を勉強したので、そのアウトプットとしてブログに書こうと思います。今回はSを冠するSingle Responsibility(単一責任の原則)について。

SOLID原則を含むデザインパターン(きれいにコードを書くためのパターン)を勉強したいUnity民のために、Unity公式から凄く分かりやすいチュートリアルが用意されているので、本ブログではこれを参考にさせて頂きながら進行していきます。

Unity公式によるGame Programming Patterns Demo
github.com

その他参考記事
qiita.com

単一責任の原則

1つのクラスは1つの役割だけを持っているべきだ」という原則です。裏を返せば、2つ以上の役割を持つような何でも屋さんクラス(神クラス)は作るべきではないという主張になります。

簡単な例としては、以下の点に注意するべきです。

  • デカすぎるクラス名(PlayerManager→PlayerAudioとPlayerInput等に分散)
  • 当初の予定から外れたメソッドを実装していないか(ManagerがTextを変更したりしてるとぐちゃぐちゃになってる)

例(Unity公式プロジェクトより引用、翻訳)

github.com

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace DesignPatterns.SRP
{
    // このクラスは短いですが、それでも単一責任の原則に反しています。
    // 多すぎる役割はクラスを更新する原因になりますし、クラスの拡張がより難解になります。

    public class UnrefactoredPlayer : MonoBehaviour
    {

        [SerializeField] private string _inputAxisName;

        [SerializeField] private float _positionMultiplier;

        private float _yPosition;

        private AudioSource _bounceSfx;

        private void Start()
        {
            _bounceSfx = GetComponent<AudioSource>();
        }

        private void Update()
        {
            float delta = Input.GetAxis(_inputAxisName) * Time.deltaTime;

            _yPosition = Mathf.Clamp(_yPosition + delta, -1, 1);

            transform.position = new Vector3(transform.position.x, _yPosition * _positionMultiplier, transform.position.z);
        }

        private void OnTriggerEnter(Collider other)
        {
            _bounceSfx.Play();
        }
    }

}

Playerクラスの役割が「移動、方向のインプット管理、オーディオの発生」と分かれています。現状はまだ綺麗ですが、これからジャンプや攻撃等を追加するたびに肥大化する構造になっており、容易にコードが煩雑になることが予想できます。

解決法

分割したクラスは今後勉強するSOLID原則に従ってもっと綺麗にすることも可能です(interfaceの実装などができる)が、単一責任の原則を保守するという今回の目的を達成するだけであれば、素直にクラスを分割するだけで解決します。

Unityは便利で、分割したクラスも全部同じPlayerオブジェクトにアタッチしてしまえばそれだけで全部機能するので、この操作だけで十分可読性を向上しながら単一責任の原則を守ることが可能です。

小技として、Unityには[RequireComponent]という属性が用意されています。これをPlayerクラスに書いておけば、うっかりコンポーネントをアタッチするのを忘れることを防止できます。細かいですが書いた方がより堅実でしょう。

docs.unity3d.com

解決例については先ほどのリンクに載っているので、そちらを参照ください。