MVI (Model-View-Intent) を学ぶために、Cycle.js の Observable のチュートリアルから Passive と Reactive の違いの解説を翻訳しました。ライセンスは原文と同じ MIT ライセンスとします。筆者の André Staltz さんが公開しているスライド (How Reactive Programming can help reduce code spaghetti ) は補助資料になるでしょう。Staltz さんは「Reactive ではデータの変更は遠くから観察される」「Passive ではデータは遠くから変更される」とも述べています (twitter、Redux と RxJS の違いは?)。
Reactive プログラミング
モジュール Foo とモジュール Bar があるとします。モジュールは OOP クラスのオブジェクトもしくは状態をカプセル化するためのほかのメカニズムと見なすことができます。
すべてのコードは何らかのモジュールに所属することを前提とします。Foo から Bar に矢印が伸びている場合を考えてみましょう。矢印は Foo が Bar 内部にある状態に何らかの影響を与えていることを示します。
このような矢印の現実の例として、Foo がネットワークリクエストを行ったり、Bar でカウンターの値を増やすなどが挙げられます。すべてのコードが何かのモジュールに所属するのであれば、 この矢印はどこに所属するでしょうか? どこでそれが定義されるのでしょうか?よくある選択肢はカウンターの数を増やすために Bar のメソッドを呼び出す Foo 内部で書くことです。
// Foo モジュールの内部
function onNetworkRequest() {
// ...
Bar.incrementCounter();
// ...
}
Foo は「ネットワークリクエストが発生したら、Bar でカウンターの数を増やす」というリレーションを所有しているので、矢印の後ろはすなわち Foo にあると言えます。
Bar は passive であるといいます。Bar は自身の状態をほかのモジュールによって変更されることを許可します。Foo は proactive であるといいます。Bar の状態関数を正しくすることに責務をもちます。passive なモジュールは自身に影響を与える矢印の存在を認識しません。
このアプローチの代替案は、矢印の向きを反転させることなく、矢印の所有者を反転させることです。
このアプローチでは、Bar は Foo のなかで起きたイベントをリスニングし、イベントが発生したら、自分の状態を管理します。
// Bar モジュール内部
Foo.addOnNetworkRequestListener(() => {
self.incrementCounter(); // self は Bar
});
Bar は reactive であるといいます。外部のイベントに反応することで、自分自身の状態を管理することに責任をもちます。一方で、Foo は自分自身のネットワークリクエストイベントから由来する矢印の存在を認識しません。
このアプローチの恩恵は何でしょうか?制御の反転です。Bar が自分自身に対して責務をもつことが主な理由です。加えて、Bar の incrementCounter()
をプライベート関数として隠すことができます。passive の場合では、incrementCounter()
をパブリックにすることが要求され、Bar の内部状態管理を外側に露出することを意味しました。Bar のカウンターが動くかどうかを理解したい場合、コードベースで使われるすべての incrementCounter()
を見つかることが必要になることも意味します。この点について、Reactive と Passive はお互いが対になる存在のように思えます。
Passive | Reactive | |
---|---|---|
Bar はどのように動くのか? | 使い方を見つける | 内部を見る |
一方で、Reactive パターンを適用する場合、Listenable モジュールのイベントによって影響を受けるモジュールを見つけたい場合、そのイベントのすべての使用例を見つけなければなりません。
Proactive | Listenable | |
---|---|---|
どのモジュールが影響を受けるのか? | 内部を見る | 使い方を見つける |
手続き型のプログラマーにとって、Passive/Proactive プログラミングはデフォルトの働き方です。Reactive パターンはたまに散発的に使われます。Reactive プログラミングのセールスポイントは自分自身に責任をもつモジュールをつくり、外部の状態を変更することよりもモジュール自身の機能に焦点を合わせることです。このことは 関心の分離 につながります。
Reactive プログラミングでの挑戦は Passive/Proactive のアプローチを考える前に Reactive/Listenable のアプローチをデフォルトで選ぶ試みです。Reactive を最初に考えるためにあなたの脳の配線を組み替えた後、学習曲線は平坦になり、RxJS のような Reactive ライブラリを使うときに、大半のタスクが直感的なものになります。