iT邦幫忙

2021 iThome 鐵人賽

DAY 5
1
Mobile Development

Jetpack Compose X Android Architecture X Functional Reactive Programming系列 第 5

Reactive programming

在上一篇中我們完成了 StickyNote 的 UI 跟 Model 的部分,後面的章節將有很大的一部分會用到 Reactive programming 的概念,在這裡先做個簡單介紹。

從現在開始我會使用下面幾個縮寫:

  • FP: Functional Programming
  • OOP: Object Oriented Programming
  • RP: Reactive Programming

Programming paradigm

我們很常會看到在介紹 RP 或是 RxJava 的人會說 RP 使用起來比較簡潔,但是以我這幾年的學習經驗來說,RP 並沒有讓程式碼變得更簡潔乾淨,相反的,在錯誤的使用情況下,反而會讓程式碼更加的混亂。依我的看法, 如果想要了解 RP、FP 跟 OOP 他們的差別的話,應該要去了解他們的切入點或是解決問題思考的方式,通常我們稱這個叫 Programming paradigm 。

在寫 RP 時,你可以想像你在一個水管的世界,在上游的水管中倒水進去的話,下游的水管出口在之後就會看到有水跑出來,而這些水就是我們所要處理的事件。只要管線是在連結的狀態,不管你在哪個時間點倒水進去,倒多少水進去,你都可以期待這些水都會在之後被下游接收,而且會依照順序的流出來,為了更好解釋 RP 的概念,接下來我將會用水分子的方式來解說。

在 RP 中,我們會期待不只一個水分子會流過水管,因為只有一個水分子流過去的話,就有點大材小用,我們花了很多時間建造這些水管,結果只使用一次,這不是很可惜嗎?所以我們期待的會是會有很多的水分子流過水管,在這過程中有可能會發生停只倒水,也可能經過生鏽的水管而讓流出來的水是黃色的,也有可能中間的水管太細所以新的水無法繼續灌進去。水管的源頭也可能不只一個,流出去的地方也不只一個。

如果說 RP 是一個水管世界的話,那 FP 我就會覺得是一個數學的世界,在數學的世界裡所有函式都是可預期(Deterministic)的,有輸入也有輸出,同樣的輸入將會得到同樣的輸出(Pure function), 一個輸入的參數你也不會預期他會突然改變本身的狀態(immutable),因此處理檔案讀寫或是網路連線狀態就不是 FP 非常擅長的事情,需要額外做一些的處理,在 FP 中使用他們(Monad)的學習成本也相對的高。

那 OOP 呢?OOP 對我來說,他的思考方式是專業分工,不同的角色有不同的職責,也可以將任務交給別人完成,但你不需要知道怎麼完成,以及任務的細節。對於同樣的任務來說 A、B、C都可以完成,但他們完成的方式跟結果可能會完全不一樣(設計模式)。但是在傳遞任務的過程要小心,接收方有可能亂改任務內容,或是出一個大包,結果連累整個任務爆炸,那這個問題是怎麼產生出來的呢?不知道,因為在設計不好的情況下,每個人都有權限修改任務內容,所以也不知道是誰修改的。

  • Reactive programming: 事件流的設計方式,不適合只有單一事件的問題。
  • Functional programming: 數學以及函式的世界,適合用來寫核心的商業邏輯。
  • Object oriented programming: 適合用來定義程式的架構以及各角色之間的交互關係。

RP ,FP, OOP 是可以共同存在的,在進行專案開發時不需要去嚴格限制全部都要使用某一種 Programming paradiam,建議大家依據需求、要解決的問題去選擇適合你的開發方式。

Reactive Programming

基本上 RP 有兩個最主要的角色,ObservableObserverObservable 是事件發送方,Observer 是事件接收方。除此之外,這兩個角色需要產生連結,產生連結的這個動作叫做 bind ,或是以 RxJava 來說,這個動作就是 subscribe ,如下圖所示。

Screen Shot 2021-08-25 at 8.56.34 PM.png

產生連結之後,任何從Observable 發出的事件,跟這個Observable 有連結的 Observer 就會接收到這些事件,如同上圖所示,每個小圓點各代表一個事件,除此之外,Observable 也可以分發事件給不同的Observable ,或是彙整多個Observable 的事件成一個單一個Observable 。前者這個行為我們稱作 Multicasting,後者的這個行為則稱為 Merge。

Screen Shot 2021-08-25 at 9.06.28 PM.png

講到這邊可能還是有點抽象,這邊用手勢事件來做解說吧!手勢操作的事件可以簡單地分成三類: Finger Down, Finger Up, Finger Move, 分別為手指觸控螢幕、手指離開螢幕、手指移動,我們可以將一系列的事件做成一張圖表,如下圖:

Screen Shot 2021-08-25 at 9.19.21 PM.png

上方圖表所描述的事情是,一開始發出了一個 Finger Down 事件,接著連續發出了四個 Finger Move 事件,最後以一個 Finger Up 來結束。所以這是代表著什麼呢?代表使用者可能想要拖曳一個物件,從 A 點移到 B 點,所以我們可以簡化使用者的意圖,用下面這張圖來詮釋:

Screen Shot 2021-08-25 at 9.26.04 PM.png

Finger Up 跟 Finger Down 事件被省略了,因為對於只要關注“拖曳”這件事的接收方來說,他們只需要知道位移量就好,什麼時候把手指放開的這個資訊對現在來說一點都不重要。接著再看下面這個範例:

Screen Shot 2021-08-25 at 9.30.36 PM.png

依上圖來看,手指在螢幕上點擊了三次,前面有單獨的一次,以及後面快速點擊兩次,所以依使用者的意圖,應該可以轉變成下面這樣子:

Screen Shot 2021-08-25 at 9.35.37 PM.png

但是使用者的意圖真的是這樣子嗎?還是使用者就是想要藉由雙重點擊來呼叫出選單呢?如下圖:

Screen Shot 2021-08-25 at 9.40.05 PM.png

所以這些事件可以從原始的、比較單純的手勢事件,藉由分析意圖,轉化為更高階、更抽象的概念,有了這些轉換過後的事件,關注這些事件的接收方,就可以不用管 Finger Up 或是 Finger Down 這種接近底層行為的細節,直接使用這些高階概念來應用。以下圖來做示範:

Screen Shot 2021-08-25 at 9.54.56 PM.png

如果我們把時間軸拉長,可以看到更多轉換過後的事件,以上圖來說,依序為 Tap, Double Tap, Drag, Drag, Drag, Tap。這些事件在不同的使用情境中可以發揮不同的效果,例如第三個時間軸的使用情境可能是雙擊打開選單,第四個時間軸的使用情境是選擇照片或是選擇商品。注意到了嗎?這每個時間軸都可以是一個Observable ,而第二個時間軸就是從第一個時間軸轉換過來的,也就是說從第一個Observable ,做一些計算,可以產生出第二個更好用的Observable ,接著第三跟第四個時間軸則是從第二個 Observable 分發出去,也就是之前所提到的 Multicasting 這個概念。

小結

今天做了一個 RP 的簡單介紹,相信相關的教學在網路上可以找到一大堆,不知道大家比較喜歡看到怎樣的教學方式呢?直接看程式碼呢?還是像本文一樣從觀念出發?還是在開發過程中有遇到什麼問題呢?歡迎大家在底下留言跟我分享你的看法吧!


上一篇
繪製便利貼以及定義模型
下一篇
你的 MVVM 不是你的 MVVM
系列文
Jetpack Compose X Android Architecture X Functional Reactive Programming30

尚未有邦友留言

立即登入留言