今回はmusicLineの内部実装の話。
Transform
(スクロール量等の画面状態)の情報を末端のクラスへどのように共有するか(依存性の注入)について考えてみました。
はじめに
musicLineでは、画面上に音符を表示して、タップやスワイプにより音符を編集します。そのため、 音符位置をスクリーン座標系へ座標変換するTransform
(スクロール量等の画面状態)の情報が必要になります。
座標変換の詳細はこちら
Transform
は作曲の際に必要となる情報で、ピアノやスクロールエリアをスクロールすることで状態を変更します。そのため、音符や曲の情報というよりは作曲全体の情報なので、ルートのモデルでTransform
を保持することが自然だと考えられます。
そうした時に、musicLineの作曲モデルを大まかに表すとこうなります。
Model
で編集中のSong
とTransform
を保持しています。Song
からTrack
、Phrase
、Note
と枝分かれしてインスタンス数が多くなり広がっていきます。
ここでTransform
とNote
に注目するとルートと末端の関係になっています。ルートのModel
ではTransform
を保持しているため、取得・編集することができますが、画面に音符を表示するためには末端のNote
でもTransform
が必要です。
ルートで保持しているTransform
を末端のNote
へどのように共有するべきでしょう。
インスタンスの共有方法
ルートで保持しているインスタンスを末端でも参照したいような状況で、インスタンスを共有する方法は次の3パターンくらいあると思います。
インスタンスを下層へ伝搬する
利点 | 依存方向が分かりやすい |
欠点 | 不要なプロパティが増える |
作成時にコンストラクタでインスタンスTransform
を渡す方法です。
単純に親から子へTransform
を伝搬するだけなので、依存方向がわかりやすいです。依存方向がわかりやすいと、階層的なデータ構造では影響範囲がわかりやすくなります。
しかし、末端までTransform
を伝搬していくためには、その間のクラスを跨ぐ必要があります。そのため、階層が深いほど不要にTransform
を保持しなければいけない状況が増えます。
インスタンス使用をルートに集約する
利点 | 参照場所がまとまる |
欠点 | 末端だけで処理できない |
インスタンスTransform
を保持しているクラスを介す方法です。
例えば、音符を表示するときは座標をNote
から直接取得するのではなく、Transform
を保持しているModel
で座標変換してから取得します。
つまり、末端でTransform
を保持するのではなく、関数を使う時にTransform
を渡します。これにより、Transform
が散らばることを防ぎ、インスタンスの管理が容易になります。
しかし、末端だけではTransform
を利用した処理ができず、必ず上層でハンドリングする必要があります。
シングルトンでインスタンスを1つにする
利点 | どこからでも参照可能 |
欠点 | 複数の状態を持てない |
インスタンスTransform
をシングルトンとして独立させる方法です。
エンティティに保持させようとはせずに、絶対的な存在として1つのTransform
をどこからでもアクセスできるようにします。
しかし、必ず1つの状態しか持てないため、状態が複数になる可能性があるときは使用できません。
また、シングルトンはグローバル変数のようなものなので不必要にシングルトンを利用すると、影響範囲が絞り込めず、依存関係が分かりずらくなります。
まとめ
利点 | 欠点 | |
下層へ伝搬 | 依存方向が分かりやすい | 不要なプロパティが増える |
ルートに集約 | 参照場所がまとまる | 末端だけで処理できない |
シングルトン | どこからでも参照可能 | 複数の状態を持てない |
基本は単純に下層へ伝搬することで良いですが、階層が深くなるとルートに集約することやシングルトンを検討した方がスッキリすると思います。
今回のパターンのようにTransform
で表示するだけの場合は、ルートに関数を作って座標変換後のNote
座標を取得することでも良いような気がします。しかしNote
は表示するだけではなく、タップや矩形選択に必要な当たり判定の処理があったり、他にも音符に関する役割を色々と与えたくなると考えられます。その際に、Transform
が必要な処理をルートに集約させるとルートクラスが肥大化する問題があります。(サービスクラスに分散させる等の工夫もできますが。。)
また、オブジェクト指向的にもNote
に関する処理はNote
自身でTransform
にアクセスして処理した方がわかりやすくなります(関心の分離)。
シングルトンパターンを利用することで上記の問題は解決できますが、シングルトンを多用すると依存関係が複雑になり、網の目参照や循環参照になってしまう危険性があります。依存関係が複雑にならないようにできる限り階層的なデータ構造にモデリングし、最低限のシングルトンに抑えた方が望ましいです。
終わりに
今回はTransform
を末端のNote
へ共有する方法について考えてみました。
シングルトンパターンを使うと綺麗なコードになりますが、依存関係が複雑になるため注意です。そのため、シングルトンを使えばいいというわけではなく、もう少し深く考える必要がありそうです。
次回はシングルトンの上手な活用法について考えてみたいと思います。