ざっくりコンポジション【Unity/C#】

## **はじめに**
「[Applibot Advent Calendar 2023](https://qiita.com/advent-calendar/2023/applibot)」14日目の記事になります。
前回の記事は[コチラ](https://qiita.com/kumachaso2525/items/331701b4d8c13611a1bc)です。

今回は、どういう時にコンポジションで持った方がいいんだっけというのを改めて考える機会があったので自分なりにまとめてみました。割と継承とコンポジションで比較されることがありますが、そんなときにコンポジションを選択するべきときについて記載しています。
例として Unity/C# でのコードを記載しています。



## **コンポジションとは**

委譲、合成とも呼びます。厳密には使い分けがあるっぽい？です。
ざっくりコードで書くと
```csharp
public class Enemy
{
    private float _attackPower;
    private float _hp;

    public Enemy(float attackPower, float hp)
    {
        _attackPower = attackPower;
        _hp = hp;
    }
}
```
が
```csharp
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`と役割が違うので分けた方がわかりやすいよね、という感じです。
当たり前と言えば当たり前ですが、クラス分けをする際に切り分ける指標として役割ベースで分けていくと考えやすいと思いました。正直ここではメリットが少ないですが、他にメリットがあるので紹介していきます。
コンポジションで持つメリットとしては、
- 必要な要素だけを公開することができる
- 名前空間、クラスを分けることで責任の所在・役割を明確に分けることができる
- 変更に強い

この３つがあると思っています。
この例として、UnityUI の表示を`CanvasGroup`で操作するコンポーネントについて考えてみます。
### メリット１：必要な要素だけを公開することができる

`CanvasGroup`のアルファを変える時に .alpha でアルファをセットできるがここを数値で指定ではなく、表示と非表示のみの機能に制限したいとします。その場合`CanvasGroup`に拡張メソッドを生やしても、継承したとしても（そもそも Unity の`CanvasGroup`が`sealed`なので継承できないが）`alpha` にはアクセスできてしまいます。
そこでコンポジションとして`CanvasGroup`を持った別クラスを用意することで、`.alpha`へのアクセスを制限することができ、`.alpha`の値は絶対`SetViewActive`を通してでしか変更できないようにできます。
```csharp
public class UIVisibilityController : MonoBehaviour
{
    [SerializeField] private CanvasGroup _canvasGroup;

    public void SetViewActive(bool isActive)
    {
        _canvasGroup.alpha = isActive ? 1f : 0f;
    }
}
```
### メリット２：名前空間、クラスを分けることで責任の所在・役割を明確に分けることができる
さらにこれを独自の`namespace`に入れておくことで、Unity 側の機能である`CanvasGroup`と自分の書いたコードで責任を明確に分けることができ、バグが起きた時に原因の特定がしやすくなります。
### メリット３：変更に強い
そして、そもそも表示非表示の切り替えを`CanvasGroup`以外のもので管理したいとなった場合にはコンポジションで持つクラスと`SetViewActive`の中の実装を変えるだけで済むため、変更に強いということになります。
## おまけ
実際`CanvasGroup`をラップして持つなら、Unity の`CanvasGroup`側を Inspector からいじられたくないのでGUI表示をいじらないようにするべきだな、と書いてて思いました。
コード書くならこんな感じのイメージです。
```csharp:UIVisibilityController.cs
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>();
    }
}
```
```csharp:CanvasGroupEditor.cs
using UnityEditor;
using UnityEngine;

[CustomEditor(typeof(CanvasGroup))]    
public class CanvasGroupEditor : Editor
{
    public override void OnInspectorGUI()
    {
        // 表示自体消してしまうとエディタでのデバッグがしにくくなるためdisableで表示
        EditorGUI.BeginDisabledGroup(true);
		// デフォルトのCanvasGroupのInspectorを表示
        base.OnInspectorGUI();
    }
}
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2998005/7d4cb5fb-cd41-5c41-c71f-c1f1d907ee0f.png)


## **終わりに**

Unity/C# を例にコンポジションの採用基準・メリットについて紹介しました。もし上記のメリットに利を感じたときはコンポジションとして持つことを検討してみてください。

以上、「[Applibot Advent Calendar 2023](https://qiita.com/advent-calendar/2023/applibot)」14日目の記事でした！
明日は、[@nakashii_](https://qiita.com/nakashii_)さんの記事です！よろしくお願いします！

最後まで読んでいただきありがとうございました。