はじめに
「Applibot Advent Calendar 2023」14日目の記事になります。
前回の記事はコチラです。
今回は、どういう時にコンポジションで持った方がいいんだっけというのを改めて考える機会があったので自分なりにまとめてみました。割と継承とコンポジションで比較されることがありますが、そんなときにコンポジションを選択するべきときについて記載しています。
例として Unity/C# でのコードを記載しています。
コンポジションとは
委譲、合成とも呼びます。厳密には使い分けがあるっぽい?です。
ざっくりコードで書くと
public class Enemy
{
private float _attackPower;
private float _hp;
public Enemy(float attackPower, float hp)
{
_attackPower = attackPower;
_hp = hp;
}
}
が
public class Enemy
{
private Status _status;
public Enemy(Status status)
{
_status = status;
}
}
public class Status
{
public Status(float attackPower, float hp)
{
AttackPower = attackPower;
Hp = hp;
}
public float AttackPower { get; }
public float Hp { get; }
}
こうなるイメージです。
Status
は明確にEnemy
と役割が違うので分けた方がわかりやすいよね、という感じです。
当たり前と言えば当たり前ですが、クラス分けをする際に切り分ける指標として役割ベースで分けていくと考えやすいと思いました。正直ここではメリットが少ないですが、他にメリットがあるので紹介していきます。
コンポジションで持つメリットとしては、
- 必要な要素だけを公開することができる
- 名前空間、クラスを分けることで責任の所在・役割を明確に分けることができる
- 変更に強い
この3つがあると思っています。
この例として、UnityUI の表示をCanvasGroup
で操作するコンポーネントについて考えてみます。
メリット1:必要な要素だけを公開することができる
CanvasGroup
のアルファを変える時に .alpha でアルファをセットできるがここを数値で指定ではなく、表示と非表示のみの機能に制限したいとします。その場合CanvasGroup
に拡張メソッドを生やしても、継承したとしても(そもそも Unity のCanvasGroup
がsealed
なので継承できないが)alpha
にはアクセスできてしまいます。
そこでコンポジションとしてCanvasGroup
を持った別クラスを用意することで、.alpha
へのアクセスを制限することができ、.alpha
の値は絶対SetViewActive
を通してでしか変更できないようにできます。
public class UIVisibilityController : MonoBehaviour
{
[SerializeField] private CanvasGroup _canvasGroup;
public void SetViewActive(bool isActive)
{
_canvasGroup.alpha = isActive ? 1f : 0f;
}
}
メリット2:名前空間、クラスを分けることで責任の所在・役割を明確に分けることができる
さらにこれを独自のnamespace
に入れておくことで、Unity 側の機能であるCanvasGroup
と自分の書いたコードで責任を明確に分けることができ、バグが起きた時に原因の特定がしやすくなります。
メリット3:変更に強い
そして、そもそも表示非表示の切り替えをCanvasGroup
以外のもので管理したいとなった場合にはコンポジションで持つクラスとSetViewActive
の中の実装を変えるだけで済むため、変更に強いということになります。
おまけ
実際CanvasGroup
をラップして持つなら、Unity のCanvasGroup
側を Inspector からいじられたくないのでGUI表示をいじらないようにするべきだな、と書いてて思いました。
コード書くならこんな感じのイメージです。
using UnityEngine;
[RequireComponent(typeof(CanvasGroup))]
public class UIVisibilityController : MonoBehaviour
{
[SerializeField] private CanvasGroup _canvasGroup;
public void SetViewActive(bool isActive)
{
_canvasGroup.alpha = isActive ? 1f : 0f;
}
// エディタでアタッチ時にコンポーネントを取得
private void Reset()
{
_canvasGroup = GetComponent<CanvasGroup>();
}
}
using UnityEditor;
using UnityEngine;
[CustomEditor(typeof(CanvasGroup))]
public class CanvasGroupEditor : Editor
{
public override void OnInspectorGUI()
{
// 表示自体消してしまうとエディタでのデバッグがしにくくなるためdisableで表示
EditorGUI.BeginDisabledGroup(true);
// デフォルトのCanvasGroupのInspectorを表示
base.OnInspectorGUI();
}
}
終わりに
Unity/C# を例にコンポジションの採用基準・メリットについて紹介しました。もし上記のメリットに利を感じたときはコンポジションとして持つことを検討してみてください。
以上、「Applibot Advent Calendar 2023」14日目の記事でした!
明日は、@nakashii_さんの記事です!よろしくお願いします!
最後まで読んでいただきありがとうございました。