2013年7月14日 星期日

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 (或是偷懶不做 ...), 會比較辛苦。

沒有留言:

張貼留言

在 Fedora 下裝 id-utils

Fedora 似乎因為執行檔撞名,而沒有提供 id-utils 的套件 ,但這是使用 gj 的必要套件,只好自己編。從官網抓好 tarball ,解開來編譯 (./configure && make)就是了。 但編譯後會遇到錯誤: ./stdio.h:10...