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

Unity 3D Framework Designing(1)—— MVVM 模式的设计和实施(Part 2)的更多相关文章

  1. Unity 3D Framework Designing(1)—— MVVM 模式的设计和实施(Part 1)

    初识 MVVM 谈起 MVVM 设计模式,可能第一映像你会想到 WPF/Sliverlight,他们提供了的数据绑定(Data Binding),命令(Command)等功能,这让 MVVM 模式得到 ...

  2. Unity 3D Framework Designing(3)——构建View和ViewModel的生命周期

    > 对于一个View而言,本质上是一个MonoBehaviour.它本身就具备生命周期这个概念,比如,Awake,Start,Update,OnDestory等.这些是非常好的方法,可以让开发者 ...

  3. Unity应用架构设计(1)—— MVVM 模式的设计和实施(Part 1)

    初识 MVVM 谈起 MVVM 设计模式,可能第一映像你会想到 WPF/Sliverlight,他们提供了的数据绑定(Data Binding),命令(Command)等功能,这让 MVVM 模式得到 ...

  4. Unity 3D Framework Designing(4)——设计可复用的SubView和SubViewModel(Part 1)

    『可复用』这个词相信大家都熟悉,通过『可复用』的组件,可以大大提高软件开发效率. 值得注意的事,当我们设计一个可复用的面向对象组件时,需要保证其独立性,也就是我们熟知的『高内聚,低耦合』原则. 组件化 ...

  5. Unity 3D Framework Designing(7)——IoC工厂理念先行

    一谈到 『IoC』,有经验的程序员马上会联想到控制反转,将创建对象的责任反转给工厂.IoC是依赖注入 『DI』 的核心,大名鼎鼎的Spring框架就是一个非常卓越的的控制反转.依赖注入框架.遗憾的是, ...

  6. Unity 3D Framework Designing(9)——构建统一的 Repository

    谈到 『Repository』 仓储模式,第一映像就是封装了对数据的访问和持久化.Repository 模式的理念核心是定义了一个规范,即接口『Interface』,在这个规范里面定义了访问以及持久化 ...

  7. Unity 3D Framework Designing(6)——设计动态数据集合ObservableList

    什么是 『动态数据集合』 ?简而言之,就是当集合添加.删除项目或者重置时,能提供一种通知机制,告诉UI动态更新界面.有经验的程序员脑海里迸出的第一个词就是 ObservableCollection.没 ...

  8. Unity应用架构设计(1)—— MVVM 模式的设计和实施(Part 2)

    MVVM回顾 经过上一篇文章的介绍,相信你对MVVM的设计思想有所了解.MVVM的核心思想就是解耦,View与ViewModel应该感受不到彼此的存在. View只关心怎样渲染,而ViewModel只 ...

  9. Unity 3D Framework Designing(2)——使用中介者模式解耦ViewModel之间通信

    当你开发一个客户端应用程序的时候,往往一个单页会包含很多子模块,在不同的平台下,这些子模块又被叫成子View(视图),或者子Component(组件).越是复杂的页面,被切割出来的子模块就越多,子模块 ...

随机推荐

  1. Openstack容器项目之Magnum

    本文以Newton版本为例. 1.Magnum简介 Magnum项目通过Openstack API能够在Openstack中创建基于容器的服务,但它本身并不直接对容器进行操作,而是通过Kubernet ...

  2. IOS任务管理之NSThread使用

    前言: 无论是Android还是IOS都会使用到多任务,多任务的最小单元那就是线程,今天学习总结一下IOS的NSThread使用. NSThread使用? 第一种方式实例化 //selector :线 ...

  3. 11g默认审计选项

    [注:参考了maclean的网文]11g默认审计选项AUDIT_TRAIL参数的缺省值为DB,审计数据记录在数据库中的AUD$审计字典基表上.在11g中CREATE SESSION将被作为受审计的权限 ...

  4. php常见面试问题

    1. 如果没有开启cookies,session如何工作? PHP中的sessions通常会使用cookies的方法.但是如果没有cookies(浏览器禁用cookies),PHP sessions也 ...

  5. 面向对象重写(override)与重载(overload)区别 (转)

    一.重写(override) override是重写(覆盖)了一个方法,以实现不同的功能.一般是用于子类在继承父类时,重写(重新实现)父类中的方法. 重写(覆盖)的规则: 1.重写方法的参数列表必须完 ...

  6. Javascript学习十

    认识DOM 文档对象模型DOM(Document Object Model)定义访问和处理HTML文档的标准方法.DOM 将HTML文档呈现为带有元素.属性和文本的树结构(节点树). 先来看看下面代码 ...

  7. css修炼宝典

    前端岗位目前确实十分火热,但是就业压力也很大:前一段时间与大学同学交谈,他向我哭诉说去一个机构学习了前端工程师,我心底里为他高兴,因为他马上就可以月薪突破10K了,可是不幸的是他说去北京面试一个月,还 ...

  8. 第22篇 js中的this指针的用法

    前面把js的相关知识总结了下,今天把js中的上下文的this,对于强类型语言,this的用法非常的单一,因为他们没有js特有的动态绑定. 首先看下面代码: function funcA() { thi ...

  9. PHP学习笔记之PDO

    1. 何为PDO? PDO(PHP数据对象) 是一个轻量级的.具有兼容接口的PHP数据连接拓展,是一个PHP官方的PECL库,随PHP 5.1发布,需要PHP 5的面向对象支持,因而在更早的版本上无法 ...

  10. Javascript原型链和原型继承

    哇好久都没有写随笔啦,整个人都慵懒啦. 为了不让大家忘记我,把以前写过的一些慢慢发出来. 在JS 中, 有两条链子,作用域链 和 原型链. 作用域链相对容易理解,两点 - 函数限定变量作用域,就是说, ...