1. 程式人生 > >Unity應用架構設計(4)——設計可複用的SubView和SubViewModel(Part 2)

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上,點選此瞭解