Android で MVVM ならぬ“LMVC”

MVC と MVVM(参考:2015年に備えて知っておきたいリアクティブアーキテクチャの潮流)の比較:

やはり、MVC の場合と MVVM の場合で、Model の意味合いが変っており、MVVM の方の M は基本的にもう Model と呼ぶべきものではないものとなっていると思う。

MVVMPattern

基本的にプレゼンテーション層とデータ・ビジネスロジック層を分けるという観点で全体を階層化させているわけだから、MVC のように、入力と出力を分けるという観点とは根本的に異っている。MVC の場合は、入力を抽象化して内部データ化するという処理の部分と、内部データを具象化して出力するという処理の部分を交錯させないで分離するという観点で、その抽象化された内部データの属する部分を Model と呼んでいるわけだ。

なので、MVC と MVVM で名称的に共通するのは View だけであり、Model 部分は紛らわしいので、別のネーミングにすべきではなかったかと思う。むしろ、ViewModel の部分の方が、View にとっての抽象化されたデータの参照元(Model of View となる部分)としての意味で ViewModel と呼ぶのは、まだ理解しうる。Model の方は Model と呼ばずに、Logic などと名付けて LVVM とでもすべきではなかったのだろうか(LVVM では別のタームと被ってしまうから、ViewModel の方をこそ Model と呼んで、LMV (Logic-Model-View) とか LMVC (Logic-Model-ViewControl) でもいいんじゃないかと思う)。

なので以下、敢えて LMVC の用語で行こうと思うが、Logic 層と ViewControl 層の両者の中間に Model 層(俗に言う ViewModel)を設ける意義は、処理の時系列的な連動性を断ち、時間的なバッファを設けることが可能となるからだと思う。ネットワークの非同期処理などを抱える Logic 層、Activity のライフサイクルなどによって頻繁にアプリ外の都合と要因で破棄・再生成されるライフサイクルを持つ ViewControl 層で、それぞれが所属する時間の流れ、ライフサイクルの様相が大きく食い違っており、この異なるフェイズが混じり合うことないように分離するには、中間にこのような Model 層を設けてバッファとするわけである。

例えるならば、コンビニエンスストアーやスーパーストアーでの、バックヤードに相当するのが Model 層である。Logic 層に相当するのはロジスティック(問屋・運送)関係者で、彼らは店頭の事情など気にすることなく、バックヤードに運び込むことだけを考えればいいし、一方、ViewControl 層に相当する店頭に立つ販売員は、バックヤードから適宜店頭の棚に商品を補充すればよい。

LMVC 以前の従来の Logic 層と ViewControl 層が分離されておらず互いに混じり合ってしまっている状況(Activity / Fragment にネットワークロジックなども記述している状態)は、例えるならば、店頭の棚に運送業者が直接納入するので、店頭の棚の事情によって、運送業者がせっかく運んできた品物を持ち帰らされて再配達を余儀なくされたり、運送業者の納入を待って販売員が客の相手をできなかったりといったギクシャクした運営状態で例えられるだろう。

Model 層の存在・設置意義(わざわざ従来の Logic や ViewControl から分離・独立させて設ける意味)はまずはそういったことになる。

そして、一旦 Model 層を設けることについての議論が終ったならば、次は Logic 層や ViewControl 層との関係性についてである。

まず、Model 層と ViewControl 層の間の関係としては、LiveData が登場する。ViewControl 層は Android で言うところの Activity / Fragment に相当するものであり、それ自体は本来、処理としては比較的複雑・大規模なものである。通常の Model のデータを参照して、それを元に ViewControl を再構築するとなると、コードがその分、肥大化する。LiveData の場合は、画面の中の一部の文字列だけ、Model のデータの変化に応じて描き変えたいといったような場合に、関数型プログラミング的なアプローチでコードを記述することが可能になる。関数型プログラミングでは、関数を記述することに主眼があり、「再度変数に代入し直す」という手続処理的な記述(ボイラープレートコード化すること)を回避できる。LiveData によって、このような明示的な再代入手続をコードの記述上、避けることができるのである(API 側が裏側でやってくれる)。

つまり、Model に対する ViewControl の処理は、LiveData によってかなりスッキリと記述できるようになるわけである。Model の該当する変数の変化を ViewControl に暗黙的に反映できるようになる。一旦、ViewControl をそのように定義しておけば、あとは Model のデータを操作することだけにプログラマーの意識が集中できるような記述になるわけである。

また実際、水面下では、コールバックなどを通じて Model 側から Observer である ViewControl へ通知されるという仕組みになっているものと思われるが、コールバックというものが Observe したい側ではなく、Observe される側の Model 側から行うというところがミソで、これが非同期プログラミングの問題の温床となっている。Observe したい側の ViewControl が停止・破棄されてしまうと、Model 側が通知しようとするその通知先である ViewControl へのポインターが null となり、NullPointerException が発生するわけである。なので、ViewControl 側では、ライフサイクルにおける終了時に必ず、Observer 登録を解除する処理を記述せねばならず、これまた典型的なボイラープレートコードとなるわけだ。これらのボイラープレート処理が、LiveData の場合はライフサイクル面を考慮して API 側が面倒を見てくれて記述する必要がなくなるので、大いに助かるわけである。

ちなみに、DataBinding は ViewControl から一部のパラメーター(変数)を抜き出して、Model の変数として連関させて、ViewControl のオブジェクトを意識しないでも、直接各パラメーターにアクセスできるようにする感じでコードを記述できるという、あくまでも「記述上の」便宜に過ぎないと考えるといいだろう。LMVC 的な構成論とは直接関係のない話題と言うことができる。

次に、Logic と Model の関係も、Model と ViewControl の関係と同様に考えることができる。非同期でタイミングを確定できない Logic のデータを、LiveData として定義し、Model 側から Observe する関係として設計する。こうすることによって、例えばアプリケーションの終了と共に Model は ViewControl と共に停止・消滅するがそのライフサイクルによって後に非同期処理が Model へコールバックを試みることで NullPointerException につながるようなことを、LiveData を介在した Observe 設計によって防止できるわけである。

最後に、以上のようにして、Logic-Model-ViewControl で互いのライフサイクルが絡み合うことないように分離したのに、それを台無しにしかねないのが、ライフサイクルのより長い、Model 側で ViewControl (Activity / Fragment) のインスタンスへのポインターを保持したり、Logic 側で Model のインスタンスへのポインターを保持したりするような真似である。これは特に LMVC 的な話題としてではなくとも、Android のライフサイクルの関係する話題で随所に出てくる話である。要するにガーベージコレクションを妨げ、メモリーリークするので、避けなければならない。

また最後に付け加えるならば、Model(Android では ViewModel クラスの派生オブジェクトに相当)に Logic を埋め込んで統合せず、分けることを Google 公式が推奨する理由は、Logic については「concern 毎にクラスを分けるべき」というプログラミングの一般論的なポリシーによる。特に LMVC (MVVM) 固有の次元の話ではない。

コメント

このブログの人気の投稿

EP-805A 廃インク吸収パッド交換

WZR-HP-AG300H with OpenWrt

この Apple ID は、App Store で使用されたことがありません