GUI model 的介面設計心得

最近寫了稍微複雜一點的 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 (或是偷懶不做 ...), 會比較辛苦。

留言

這個網誌中的熱門文章

virtualbox 使用 USB 裝置

熟悉系統工具好處多多

如何 git merge 更改檔名的檔案