CleanArchitectureでひとつ『上』 のコードを目指す：実装編

[前回の記事(概念編)](https://qiita.com/naninunenoy/items/2b05bef337f89bdbb7c3)からの続きです。是非こちらもご覧ください。
CleanArchitectureを適用したBMI計算アプリの実装について解説します。
言語はC#、実行環境はUnityです。

## 依存
プログラムにおける依存とは主に「クラスAがクラスBに依存している」といった表現で使われます。この場合、「クラスBがクラスAの一部になっている」「クラスAはクラスBの中身を知っている」ということを表し、「クラスAはクラスBの上位モジュールである(＝クラスBはクラスAの下位モジュールである)」わけです。

<img width="240px" alt="依存" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274974/171142cb-0fd4-0b07-b8fc-064b92de9c25.png">

上位/下位という言葉を使うと上位は下位より偉いと捉えられますが、一概にそうとは言えません。クラスAがいないとクラスBの存在理由が無いのはそうなのですが、クラスAもクラスB無しでは自分の一部を失うことになります。「代わりはいくらでもいる」とはならないのです。

## 抽象
「代わりはいくらでもいる」とクラスAがクラスBに対して強く言えるようになる方法に **抽象化** があります。クラスBの機能を整理して、インタフェースBに抽象化します。クラスAはそのインタフェースBに依存するようにすれば、クラスAはクラスBに対して「うちはインタフェースBを実現しているクラスなら誰でも良いんだ。代わりはいくらでもいる。」といえるようになります。

<img width="360px" alt="抽象" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274974/bfd14497-4c11-1b27-cf17-6019434601f5.png">

抽象化を実現する方向には `interface` と `abstract class` を使う2つの方法がありますが、C#ではクラスの多重継承ができないので、`interface`を使う方が多いと思います。実装も共通化させたいなどの事情があれば`abstract class`を使ってください。

さて、CleanArchitecture以前に設計の原則として **依存性逆転の原則** [^依存性の逆転]というものがあります。
[^依存性の逆転]: [Unity開発で使える設計の話＋Zenjectの紹介](https://www.slideshare.net/torisoup/unityzenject/62) 素晴らしい資料なので是非読んでください

抽象化前はクラスAからクラスBの右向きの矢印一本しかなかったですが、抽象化によってクラスBからクラスA(インタフェースB)に向かう左向きの矢印が出来ました。これが依存性逆転です。
今までは一方的にクラスAに使われる側だったクラスBもインタフェースBを **実現してやってる** 上位の立場になったわけです。(クラスAに見捨てられても「私はインタフェースBを実現したクラスです」と宣伝すれば別のクラスに使ってもらえるかもしれません。)

## 境界を超える

CleanArchitectureのルールとして、**外側の層が内側の層に依存する** というものがあります。
画像左側の3本の並んだ矢印がそれを表しています。

<img width="540px" alt="CleanArchitecture" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274974/98650224-05fe-1352-ed70-35281525135e.jpeg">
外側が内側を知るのはいいが、内側が外側を知るのはダメということです。

前の記事でUseCaseについて
> 数字の入力をPresenter経由で受け、BMI計算を呼び出し、結果をPresenterに返す実装をUseCaseで行います。

という説明をしました。これではあたかも

```c#:BMIUseCase.cs
class BMIUseCase {
  BMIPresenter presenter = new BMIPresenter();
  ...
}
```

の様に `BMIUseCase` が `BMIPresenter` に依存した作りになっていると想像されたかもしれません。

<img width="360px" alt="BMI依存" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274974/818d1ffd-e351-a1c3-581d-cbcb6b801db8.png">

これは、「外から内に依存する(内から外に依存してはいけない)」という決まりに反しています。しかし、BMIを計算するというUseCaseを`BMIUseCase`として実装するためには数値入力のViewと接続した`BMIPresenter`が必要になります。**この内側から外側への依存を回避するために依存性逆転というトリックを使います**。

`IBMIPresenter`というinterfaceを定義します。そして、`BMIUseCase`からは`IBMIPresenter`を参照し、それを`BMIPresenter`で実現するようにします。
<img width="480px" alt="BMI抽象" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274974/92ed7da3-0994-e015-155c-4c900cf1c219.png">

ここで留意すべきはinterfaceの`IBMIPresenter`は緑の<font color="SpringGreen">**Interface Adapters**</font>ではなく赤の<font color="Salmon">**Application Business Rules**</font>に属すると言う所です。[^cafu]
理由は`IBMIPresenter`を決定するのはPresenterやViewではなく、UseCaseだからです。BMIの計算と表示を行うUseCaseで使うPresenterの定義が`IBMIPresenter`に現れていて、`BMIPresenter`はそれを実現したに過ぎないという訳です。

[^cafu]: [umm/cafu_core レイヤー定義](https://github.com/umm/cafu_core/wiki/Layer_Definition-ja) 大いに参考にさせてもらいました

これにより内側は外側を知らず、外側が内側の定義を知っているだけになり、「外から内に依存する(内から外に依存してはいけない)」というルールを守ることができます。

このように、CleanArchitectureで**境界を超える**ときは抽象化による依存性逆転が便利な場面が多いです。
前記事のDomain/UseCase/Presenter/Viewをすべて表すと以下の図になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274974/ecb6a933-7e5e-4074-b49f-cda3e0d920fd.png)

各層の外側から内側に矢印が向くように作ってあります。Domainにて抽象化を行っていない点が恣意的ですが、この層は変更が行われることが少ないことや、Domainは単にUseCaseから利用される層なのでこのようにしています。必要性を感じるなら抽象化を行ってください。

※ Domain=CleanArchitectureの図で言う所のEntitiesです。本記事ではDomainという言葉を使っています。

## 黒幕は誰だ？
UseCaseを中心に考えて実装を行うと

```c#:BMIUseCase.cs
class BMIUseCase {
  IBMIPresenter presenter;
  public BMIUseCase(IBMIPresenter presenter) {
    this.presenter = presenter;
  }
  ...
}
interface IBMIPresenter {
  ...
}
```

```c#:BMIPresenter.cs
class BMIPresenter : IBMIPresenter {
  IBMIView view;
  public BMIPresenter(IBMIView view) {
    this.view = view;
  }
  ...
}
interface IBMIView {
  ...
}
```

```c#:BMIView.cs
class BMIView : IBMIView {
  ...
}
```

のような構造になります。
`BMIUseCase`は`IBMIPresenter`に依存し、コンストラクタで外から`IBMIPresenter`を実装したクラスのインスタンスを受け取れるようになっています。
interfaceなどで抽象化した要素に具体的なインスタンスを渡して抽象を解決する処理を**依存性の注入(Dependency injection)**といいます。依存性の注入を行う人物が裏で糸を引いて、各層の実装を引き合わせることでBMIを求めるアプリが完成するのです。正に黒幕です。

![money_yami_soshiki.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274974/900eb2b4-12bf-94b7-6f79-89816f5d2d15.png)

実際に依存性の注入を行う場合ですが、プログラムの起動時に`Main`でまとめて行う場合もあれば、DIライブラリを使って行う場合などがあります。

```c#:main.cs
// mainで依存性の注入を行う例
int main(string[] args) {
  var view = new BMIView();
  var presenter = new BMIPresenter(view); // presetnerにはviewが必要
  var usecase = new BMIUseCase(presenter); // usecaseにはpresenterが必要
  ...
}
```

## Unityでの実装
Unityのバージョンは2019.1.14です。

### 画面イメージ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274974/b6789f65-9a9d-82df-bb83-0d3852430dbb.png)

身長と体重を入力できる `InputField` とBMIの結果を表示する `Text` を配置します。
名前/年齢/性別の入力は何となく付けました。

### View
UIによるユーザへの入出力を行う部分です。Unityの都合による影響をモロに受ける部分でもあります。

```c#
using UnityEngine;
using UnityEngine.UI;

namespace BMIApp {
    public interface IView { }
    public interface IBMIView : IView {
        InputField NameInput { get; }
        InputField HeightInput { get; }
        InputField WeightInput { get; }
        InputField AgeInput { get; }
        Toggle GenderMaleToggle { get; }
        Toggle GenderFemaleToggle { get; }
        Text BMIText { get; }
        Button SaveButton { get; }
    }
    public class BMIView : MonoBehaviour, IBMIView {
        [SerializeField] InputField nameInput = default;
        [SerializeField] InputField heightInput = default;
        [SerializeField] InputField weightInput = default;
        [SerializeField] InputField ageInput = default;
        [SerializeField] Toggle genderMaleToggle = default;
        [SerializeField] Toggle genderFemaleToggle = default;
        [SerializeField] Text bmiText = default;

        public InputField NameInput => nameInput;
        public InputField HeightInput => heightInput;
        public InputField WeightInput => weightInput;
        public InputField AgeInput => ageInput;
        public Toggle GenderMaleToggle => genderMaleToggle;
        public Toggle GenderFemaleToggle => genderFemaleToggle;
        public Text BMIText => bmiText;
    }
}
```

#### `IView`
CleanArchitectureに則った構成であることを示すために定義したinterfaceです。別に定義しなくても実装できない訳ではありません。

#### `IBMIView`
BMIの入力・出力を行うUIの要素群を定義したinterfaceです。<font color="SpringGreen">**Interface Adapters**</font>層の住人になります。

#### `BMIView`
`IBMIView`を実現した具体的なクラスです。Unityで動かす都合上`MonoBehaviour`を継承しします。<font color="SteelBlue">**Frameworks & Drivers**</font>層の住人になります。

### Presenter
ViewとUseCaseを繋ぐ層です。

 * UseCase **へ** のUIのイベントを通知する
 * UseCase **から** の情報をUIに反映する

という双方向のデータ処理を実現する必要があります。UseCaseへの通知を今回は`UniRx`を導入し、`IReadOnlyReactiveProperty`を使って実現しました。

```c#
using System;
//using UnityEngine.UI; 禁止!!
using UniRx;

namespace BMIApp {
    public interface IPresenter { }
    public interface IBMIPresenter : IPresenter {
        IReadOnlyReactiveProperty<string> NameInput { get; }
        IReadOnlyReactiveProperty<string> HeightInput { get; }
        IReadOnlyReactiveProperty<string> WeightInput { get; }
        IReadOnlyReactiveProperty<string> AgeInput { get; }
        IReadOnlyReactiveProperty<bool> GenderMaleSelect { get; }
        IReadOnlyReactiveProperty<bool> GenderFemaleSelect { get; }
        void Bind();
        void SetBMIResult(string result);
    }
    public class BMIPresenter : IBMIPresenter {
        readonly IBMIView view;
        public BMIPresenter(IBMIView view) { this.view = view; }

        public IReadOnlyReactiveProperty<string> NameInput { private set; get; }
        public IReadOnlyReactiveProperty<string> HeightInput { private set; get; }
        public IReadOnlyReactiveProperty<string> WeightInput { private set; get; }
        public IReadOnlyReactiveProperty<string> AgeInput { private set; get; }
        public IReadOnlyReactiveProperty<bool> GenderMaleSelect { private set; get; }
        public IReadOnlyReactiveProperty<bool> GenderFemaleSelect { private set; get; }

        public void Bind() {
            NameInput = view.NameInput.OnEndEditAsObservable().ToReadOnlyReactiveProperty();
            HeightInput = view.HeightInput.OnEndEditAsObservable().ToReadOnlyReactiveProperty();
            WeightInput = view.WeightInput.OnEndEditAsObservable().ToReadOnlyReactiveProperty();
            AgeInput = view.AgeInput.OnEndEditAsObservable().ToReadOnlyReactiveProperty();
            GenderMaleSelect = view.GenderMaleToggle.OnValueChangedAsObservable().ToReadOnlyReactiveProperty();
            GenderFemaleSelect = view.GenderFemaleToggle.OnValueChangedAsObservable().ToReadOnlyReactiveProperty();
        }

        public void SetBMIResult(string result) {
            view.BMIText.text = result;
        }
    }
}
```

#### `IPresenter`
CleanArchitectureに則った構成であることを示すために定義したinterfaceです。無駄だと思うなら不要です。

PresenterでViewの要素(`Button`とか)を公開するプロパティを持ってしまうとUseCaseがViewの要素にアクセスできてしまいPresenterの層が意味を為しません。**ここでの`using UnityEngine.UI`は禁止するのが吉です**。

#### `IBMIPresenter`
BMI計算アプリUIのin/outを定義したinterfaceです。<font color="Salmon">**Application Business Rules**</font>層の住人になります。

#### `BMIPresenter`
`IBMIPresenter`を実現した具体的なクラス。`IBMIView`をコンストラクタで受け取り、そいつからのイベントを外部に公開するのと同時に、そいつへのデータの反映も行います。<font color="SpringGreen">**Interface Adapters**</font>層の住人です。

### Domain
業務ロジックの層です。BMI計算ロジックが含まれます。

```C#
namespace BMIApp {
    public interface IBMIDomainObject {
        float Height { set; get; } // [cm]
        float Weight { set; get; } // [m]
    }
    public class BMIDomain {
        public float CalcBMI(IBMIDomainObject domainObject) {
            if (domainObject.Height <= 0.0F || domainObject.Weight <= 0.0F) {
                return 0.0F;
            }
            var h = domainObject.Height / 100.0F; // cm -> m
            return domainObject.Weight / (h * h);
        }
    }
}
```

### UseCase
アプリケーションの挙動の実装を行います。Viewを直接参照せずにPresetnerから操作を行ったり、BMI計算処理などはDomainに任せたりするなどして、アプリケーションの挙動に集中させます。

```c#
using System;
using UnityEngine;
using UniRx;

namespace BMIApp {
    public interface IUseCase {
        void Begin();
    }
    public class BMIUseCase : IUseCase {
        readonly IBMIPresenter bmiPresenter;
        readonly Component disposableComponent;
        readonly BMIDomain bmiDomain;
        public BMIUseCase(IBMIPresenter bmiPresenter, Component disposableComponent) {
            this.bmiPresenter = bmiPresenter;
            this.disposableComponent = disposableComponent;
            this.bmiDomain = new BMIDomain();
        }
        public void Begin() {
            var dto = new BMIDataTransferObject();
            bmiPresenter.Bind();
            bmiPresenter
                .NameInput
                .Subscribe(x => {
                    dto.Name = x;
                })
                .AddTo(disposableComponent);
            bmiPresenter
                .HeightInput
                .Subscribe(x => {
                    if (float.TryParse(x, out var val)) {
                        dto.Height = val;
                        dto.BMI = bmiDomain.CalcBMI(dto);
                        bmiPresenter.SetBMIResult($"{dto.BMI:F1}");
                    }
                })
                .AddTo(disposableComponent);
            bmiPresenter
                .WeightInput
                .Subscribe(x => {
                    if (float.TryParse(x, out var val)) {
                        dto.Weight = val;
                        dto.BMI = bmiDomain.CalcBMI(dto);
                        bmiPresenter.SetBMIResult($"{dto.BMI:F1}");
                    }
                })
                .AddTo(disposableComponent);
            bmiPresenter
                .AgeInput
                .Subscribe(x => {
                    if (int.TryParse(x, out var val)) {
                        dto.Age = val;
                    }
                })
                .AddTo(disposableComponent);
            Observable
                .CombineLatest(bmiPresenter.GenderMaleSelect,  bmiPresenter.GenderFemaleSelect)
                .Subscribe(x => {
                    if (x[0]) {
                        dto.Gender = Gender.Male;
                    } else if (x[1]) {
                        dto.Gender = Gender.Female;
                    } else {
                        dto.Gender = Gender.None;
                    }
                })
                .AddTo(disposableComponent);
        }
    }
    public enum Gender { None = 0, Male, Female }
    public class BMIDataTransferObject : IBMIDomainObject {
        public string Name { set; get; }
        public int Age { set; get; }
        public Gender Gender { set; get; }
        public float BMI { set; get; }
    }
}
```
コンストラクタで`IBMIPresenter`を受け取り、`Begin()` にて`IBMIPresenter`が公開している`IReadOnlyReactiveProperty`をすべて購読(`Subscribe()`)します。UIの入力に応じてBMI計算ロジックを呼び出して画面に結果を反映させるなど、アプリの処理がすべて`Begin()`に記述されています。UseCaseには難しいことはさせず、子供でもできることだけを記述するのがいいでしょう。例えば入力のバリデーションをしたくなった場合にも`BMIUseCase`にべた書きするのではなく、専用のDomainやUseCaseを新たに追加することを考えてください。

### Main
今まで出てきたView/Presenter/UseCaseは全てコンストラクタで要素を受け取って依存性の注入ができるようになっています。必要なインスタンスを各自に渡して依存性の注入を行い、アプリの挙動を決定・開始させる役目をMainに任せます。
Unityの場合、Mainには`MonoBehaviour`を継承させてシーンに配置することで以下を行えるようにします。

 * `[SerializeField]`などでシーン上のUnityのコンポーネント(ここではView)を受け取る
 * `Awake()`でPresenter/UseCaseを作成
 * `Start()`でUseCaseを実行

```c#
using UnityEngine;

namespace BMIApp {
    public class BMISceneMain : MonoBehaviour {
        [SerializeField] BMIView bmiView = default; // from inspector
        IUseCase bmiUseCase;

        void Awake() {
            bmiUseCase = new BMIUseCase(new BMIPresenter(bmiView), this);
        }

        void Start() {
            bmiUseCase.Begin();
        }
    }
}
```

Mainに書くのはこれだけです(シンプルですね)。アプリのロジックは全てUseCaseを中心に構成されています。

### クラス図
各クラスの関係を表したクラス図です。UseCaseは抽象化されたPresenterしか知らず、Presenterの先にView(`MonoBehaviour`などUnityの世界)があることなど知りえないのです。Mainを全てを知る立場に添えて、裏で各要素を繋げてアプリを走らせる役割を任せています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274974/939efaf2-df82-18d8-fd9c-0773e06d7b2b.png)

## まとめ
他のCleanArchitectureの記事にある実装例とは合致していないかもしれませんが、私なりに腑に落ちるように噛み砕いてUnityの実装まで落とし込みました。作るScriptのファイルが多くなるのでいざ作ると大変ではありますが、MainやUseCaseに実装する処理が明確でシンプルになった手ごたえを感じています。また、各層が抽象化されているのでUseCaseなどをテストする場合も簡単にできそうです。

### GitHub
[naninunenoy/UnityViewPatterns/BMIApp](https://github.com/naninunenoy/UnityViewPatterns/tree/master/ViewPattern/Assets/BMIApp)
色々(シーン跨ぐ場合や複数UseCaseがある場合など)試したので、今は仮ログインや履歴表示の機能などが盛り込まれてます。

![image.png](https://github.com/naninunenoy/UnityViewPatterns/blob/master/doc/demo.gif?raw=true)