• 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上,点击此了解

  • 相关阅读:
    jdk源码剖析三:锁Synchronized
    ASP.NET的session操作方法总结
    C#文件相同性判断
    C#的DataTable操作方法
    C#二进制流的序列化和反序列化
    C#常用的IO操作方法
    C#缓存操作
    CLR中的程序集加载
    Oracle数据库的SQL分页模板
    奇妙的NULL值,你知道多少
  • 原文地址:https://www.cnblogs.com/OceanEyes/p/unity3d_framework_designing_get_started_with_mvvm_part2.html
Copyright © 2020-2023  润新知