Unity應用架構設計(4)——設計可複用的SubView和SubViewModel(Part 2)
在我們設計和開發應用程式時,經常要用到控制元件。比如開發一個客戶端WinForm應用程式時,微軟就為我們提供了若干控制元件,這些控制元件為我們提供了可被定製的屬性和事件。屬性可以更改它的外觀,比如背景色,標題等,而事件可以豐富控制元件的行為,比如最常見的『按鈕點選』,誰也不能確定點選之後將發生什麼事,是連線資料庫呢還是彈出警告框,在不同的場景下,『按鈕點選』 的行為往往呈現不一致。所以,與其舉棋不定,還不如把處理委託給開發者,這就是『OnClick』事件。
SubView行為多變性
在上篇文章中,我闡述了為什麼要使用SubView,總結起來就3個字:『可複用』 。那麼問題來了,既然是可複用,那就意味著SubView可以在任何場景下使用,那怎樣才能確保它做的是正確的行為呢?
舉個栗子,還是 以如下圖FaceBox為例,不同的場景下點選頭像應該處理不同的事:
- 在戰團中點選頭像,則顯示該成員的具體資訊
- 在隊伍裡點選頭像,則進入換人介面
- 在戰鬥時點選頭像,則顯示它配置的戰術
你看,同樣一個SubView,在不同的場景下它的行為往往是不一致的。那我們怎麼去跟蹤這些行為呢?
定製SubView的行為
你可能會以如下方式去定製SubView的行為:
void OnClick(){ if(戰團){ 顯示該成員的具體資訊 }else if(隊伍){ 進入換人介面 }else if(戰鬥){ 顯示它配置的戰術 }else{ //其他 } }
還是那句話這樣,這樣並沒有錯,甚至對某些SubView而言邏輯還很清晰。但仔細想想,這是最好的實踐嗎?
- 如果我要繼續新增一種情況,是不是隻能在else if擴充套件,違反了開閉原則,應該對擴充套件是開放的,對修改是關閉的
- 既然這個SubView是可複用的,那意味著將它放在任何專案中都是沒問題的,但實際上OnClick裡面處理了業務邏輯,緊耦合當前遊戲的業務
所以顯然上述程式碼不是最佳實踐。那我們應該怎樣去解決呢?
實際從開頭的引言我已經提出瞭解決方案,以事件的形式委託給開發者來確定。一個Button也好,還是一個SubView也好,他們都是可複用的元件,不應該與具體的業務邏輯相結合。通過事件或者委託的形式,暴露給開發者來決定究竟要處理什麼邏輯,這樣才能和具體業務邏輯解耦。
委託的介入
還是以FaceBox舉例,那麼從上面的分析得出結論,我們需要定義委託或者事件,那應該定義在FaceBoxView呢還是FaceBoxViewModel中呢?
還是那句話,View不處理具體的業務邏輯,View將請求交給ViewModel去處理。
故在FaceBoxViewModel中增加可被外界監聽的委託或者事件,我以委託舉例,實際上事件就是特殊的委託。
public class FaceBoxViewModel:ViewModelBase
{
//省略部分程式碼
public delegate void OnBeginDragHandler();
public OnBeginDragHandler OnBeginDrag;
public delegate void OnDragHandler();
public OnDragHandler OnDrag;
public delegate void OnEndDragHandler();
public OnEndDragHandler OnEndDrag;
public delegate void OnClickHandler();
public OnClickHandler OnClick;
//省略部分程式碼
}
FaceBoxView不處理具體的邏輯,交由FaceBoxViewModel去實現:
protected override void OnInitialize()
{
//省略部分程式碼
//監聽事件
var beginDragEntry = new EventTrigger.Entry();
beginDragEntry.eventID = EventTriggerType.BeginDrag;
beginDragEntry.callback.AddListener(eventData => { OnBeginDrag(); });
eventTrigger.triggers.Add(beginDragEntry);
var dragEntry = new EventTrigger.Entry();
dragEntry.eventID = EventTriggerType.Drag;
dragEntry.callback.AddListener(eventData => { OnDrag(); });
eventTrigger.triggers.Add(dragEntry);
var endDragEntry = new EventTrigger.Entry();
endDragEntry.eventID = EventTriggerType.EndDrag;
endDragEntry.callback.AddListener(eventData => { OnEndDrag(); });
eventTrigger.triggers.Add(endDragEntry);
var pointClickEntry = new EventTrigger.Entry();
pointClickEntry.eventID = EventTriggerType.PointerClick;
pointClickEntry.callback.AddListener(eventData => { OnClick(); });
eventTrigger.triggers.Add(pointClickEntry);
}
private void OnClick()
{
if (BindingContext.OnClick != null)
{
BindingContext.OnClick();
}
}
腦海裡梳理一下請求的流程:FaceBoxView.PointClick->FaceBoxViewModel.OnClick()->委託給外部的某個Handler。
小結
實際上『委託』這個概念非常重要,和具體的語言、平臺無關。比如在iOS開發經常聽到代理模式,顧名思義,將請求交給具體的處理者去處理。設計模式並不深奧,很多模式的理念都是相通的,不同的是對應語言下不同的表現形態,善於剖開現象看本質,很多都是相通的。
原始碼託管在Github上,點選此瞭解