最近寫了稍微複雜一點的 iOS 介面, 多了一些心得。先前已寫過《非同步程式心得》包含 GUI 和 IO 的體會, 這篇則著重在 model 介面的設計。
如同《從需求出發理解背後技術的思考脈胳》的體會, 思考技術議題時不仿從需求切入, 比較容易掌握到必要項目, 需要在設計上做取捨時也有個依據。
先來看兩個生活中的例子:
- 電梯外側和內側有螢幕顯示電梯目前所在的樓層。
- 紅綠燈的秒數倒數。
這兩個例子裡, 不會因為多了螢幕顯示樓層或秒數而影響結果發生的時間, 但是會讓使用者明白現在發生什麼事, 比較不會失去耐心。相信體驗過沒提供樓層的電梯或沒秒數的紅綠燈的人, 應該更能體會沒有告知狀態變化的感覺。
回到程式開發的世界裡, model 時常會有非同步的操作, 比方說寫一個線上文書編輯器, 介面可以是 Web 或是 App UI, 但資料存在線上伺服器裡。
存檔的必要介面如下:
void Document::Save(); void DocumentDelegate::OnSaveResult(bool ok);
caller 產生一個 document, 將自己傳入作為 DocumentDelegate。呼叫 Save() 之後, 會觸發 callback OnSaveResult() 告知存檔是否成功。
乍看之下這樣的介面已滿足所有需求了, 使用者要求存檔, 介面也會回應存檔成功或失敗。但是, 若 Save() 和 OnSaveResult() 之間間隔過久, 使用者不知道剛才的操作是成功或失敗, 或是他沒有按到存檔鈕 (使用觸控面版特別容易有這樣的疑問), 甚至會懷疑應用程式是否當掉了。
或許我們會考慮在呼叫 Document::Save() 之後, 順便在 UI 上顯示訊息, 讓使用者明白已經進行 Save() 的操作。單就這例子來看沒有問題, 但是當 model 操作比較複雜時就有問題了。比方說, 後來支援了自動存檔, 有可能使用者沒有按「存檔」, 但 document 定期會執行 Save(), 介面無從反應現在是否正在存檔中。
反過來說, 若是介面改為:
void Document::Save(); void DocumentDelegate::OnSaveResult(bool ok); void DocumentDelegate::OnSaving(); // NEW
UI 不會漏掉這個變化。即使有多個 UI 元件關心 Document 是否正在存檔、存檔成功或失敗, 也可透過 DocumentDelegate 讓這些 UI 元件和 Document 相依, 而不是某個 UI 元件通知其它 UI 元件, 減輕 UI 元件之間的相依性。
總結來說, 對於所有的非同步操作, model 至少需要告知下述三種狀態變化:
- 正在執行中
- 執行成功
- 執行失敗
這樣 UI 才有能力完成必要的功能。即使 UI 用不到也無所謂, 實作 model 時原本就需要考慮好可能的狀態變化, 提供對應的 callback 只是舉手之勞。反之, 實作 UI 時發覺綁手綁腳, 要再回頭思考該改 model 還是 UI (或是偷懶不做 ...), 會比較辛苦。
沒有留言:
張貼留言