Unity應用架構設計(1)—— MVVM 模式的設計和實施(Part 2)
MVVM回顧
經過上一篇文章的介紹,相信你對MVVM的設計思想有所瞭解。MVVM的核心思想就是解耦,View與ViewModel應該感受不到彼此的存在。
View只關心怎樣渲染,而ViewModel只關心怎麼處理邏輯,整個架構由資料進行驅動。不僅View與ViewModel彼此解耦,ViewModel與ViewModel之間也是解耦的。
通過訊息訂閱-釋出機制,解決了ViewModel之間的強依賴關係。
先回顧一下我們已完成的功能,Framework中最核心就是BindableProperty 類,ViewModel 中所有需要被繫結到UI 控制元件的屬性必須是一個BindableProperty 物件。它是一個職責非常單一的類,監聽Value的數值是否發生變化,當變化時,觸發OnValueChanged 事件,通知View 做出相應的更新。
public class BindableProperty<T> { public delegate void ValueChangedHandler(T oldValue, T newValue); public ValueChangedHandler OnValueChanged; private T _value; public T Value { get { return _value; } set { if (!object.Equals(_value, value)) { T old = _value; _value = value; ValueChanged(old, _value); } } } private void ValueChanged(T oldValue, T newValue) { if (OnValueChanged != null) { OnValueChanged(oldValue, newValue); } } }
那問題來了,View在何時並以怎樣的方式去監聽這些屬性的變化呢?
BindableProperty是一個很好的設計,它不僅可以用在ViewModel中,還可以用在View中,用它來修飾 ViewModel,當ViewModel 改變時,比如初始化時,或者從一個ViewModel變化到另一個ViewModel物件時,在觸發的OnBindingContextChanged 事件中實現對ViewModel中的屬性監聽。如下定義的抽象父類:UnityGuiView
public readonly BindableProperty<ViewModel> ViewModelProperty = new BindableProperty<ViewModel>(); public ViewModel BindingContext { get { return ViewModelProperty.Value; } set { ViewModelProperty.Value = value; } } protected virtual void OnBindingContextChanged(ViewModel oldViewModel, ViewModel newViewModel) { } public UnityGuiView() { this.ViewModelProperty.OnValueChanged += OnBindingContextChanged; }
子類SetupView繼承自UnityGuiView,並且Override OnBindingContextChanged,並實現對ViewModel中的屬性監聽。
protected override void OnBindingContextChanged(ViewModel oldViewModel, ViewModel newViewModel)
{
base.OnBindingContextChanged(oldViewModel, newViewModel);
SetupViewModel oldVm = oldViewModel as SetupViewModel;
if (oldVm != null)
{
oldVm.Name.OnValueChanged -= NameValueChanged;
...
}
if (ViewModel!=null)
{
ViewModel.Name.OnValueChanged += NameValueChanged;
...
}
}
進一步抽象
實際上對於ViewModel而言會有非常多的BindableProperty需要被繫結到UI控制元件中,從程式碼的可讀性而言,如下程式碼是非常沉長和囉嗦的:
if (oldVm != null)
{
oldVm.Name.OnValueChanged -= NameValueChanged;
...
}
if (ViewModel!=null)
{
ViewModel.Name.OnValueChanged += NameValueChanged;
...
}
因為+=和-=是成對出現的,所以只要是看到 OnValueChanged,這部份程式碼的長度幾乎都是*2。
仔細觀察一下,每個View都會出現 具體的 ViewModel.屬性.OnValueChanged事件+=或者-=具體的處理函式 這樣的固定模板。
那麼是否可以將這部分程式碼抽象到一個公共類中呢,並且暴露出一個簡單的方法提供給View來初始化這些OnValueChanged事件,比如:
PropertyBindingUtils.Init<string>("Color",OnColorPropertyValueChanged);
然後在Init方法中+=或者-=具體的處理函式。
當然是可以得,定義一個PropertyBinder屬性繫結器,通過反射技術,動態為屬性+=或者-= OnValueChanged 事件,腦海裡的 Raw 程式碼如下
Init<TProperty>(string propertyName ,OnValueChanged valueChangedHandler)
{
var fieldInfo = typeof(TViewModel).GetField(propertyName, BindingFlags.Instance | BindingFlags.Public);
var value = fieldInfo.GetValue(viewModel);
BindableProperty<TProperty> bindableProperty = value as BindableProperty<TProperty>;
bindableProperty.OnValueChanged += valueChangedHandler;
bindableProperty.OnValueChanged -= valueChangedHandler;
}
最核心的程式碼就那麼幾步,詳細程式碼可以檢視原始碼PropertyBinder的實現。
重構檢視基類:UnityGuiView
想象一下PropertyBinder應該放在哪兒。
它是用來監聽ViewModel中的屬性值變化的,用來替換沉長的 oldVm.Property.OnValueChanged +=和-= NameValueChanged,理所應當應該放在View中,因為每個View都需要,故將它定義在UnityGuiView 中。
又因為PropertyBinder需要知道為哪個ViewModel進行服務(因為需要反射),故通過泛型來約束 UnityGuiView< T >:IView where T:ViewModelBase 。
再對BindingContext稍作改變,當它被賦值時,只初始化一次對OnValueChanged事件的監聽(原先是放在建構函式裡)。
public readonly BindableProperty<ViewModelBase> ViewModelProperty = new BindableProperty<ViewModelBase>();
public ViewModelBase BindingContext
{
get { return ViewModelProperty.Value; }
set
{
if (!_isBindingContextInitialized)
{
OnInitialize();
_isBindingContextInitialized = true;
}
//觸發OnValueChanged事件
ViewModelProperty.Value = value;
}
}
/// <summary>
/// 初始化View,當BindingContext改變時執行
/// </summary>
protected virtual void OnInitialize()
{
//無所ViewModel的Value怎樣變化,只對OnValueChanged事件監聽(繫結)一次
ViewModelProperty.OnValueChanged += OnBindingContextChanged;
}
值得注意的事,我定義了一個virtual的OnInitialize,這樣子類可以override它從而實現一些初始化方法,比如:
protected override void OnInitialize()
{
base.OnInitialize();
Binder.Add<string>("Color",OnColorPropertyValueChanged);
}
private void OnColorPropertyValueChanged(string oldValue, string newValue)
{
switch (newValue)
{
case "Red":
buttonImage.color = Color.red;
break;
case "Yellow":
buttonImage.color = Color.yellow;
break;
default:
break;
}
}
小節
這篇部落格基本上是回顧了MVVM模式在Unity 3D上的實踐,結合自己的開發經驗,通過反射的技術可以有效減少沉長的程式碼。
原始碼託管在Github上,點選此瞭解