Unity 3D Framework Designing(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上,点击此了解
Unity 3D Framework Designing(1)—— MVVM 模式的设计和实施(Part 2)的更多相关文章
- Unity 3D Framework Designing(1)—— MVVM 模式的设计和实施(Part 1)
初识 MVVM 谈起 MVVM 设计模式,可能第一映像你会想到 WPF/Sliverlight,他们提供了的数据绑定(Data Binding),命令(Command)等功能,这让 MVVM 模式得到 ...
- Unity 3D Framework Designing(3)——构建View和ViewModel的生命周期
> 对于一个View而言,本质上是一个MonoBehaviour.它本身就具备生命周期这个概念,比如,Awake,Start,Update,OnDestory等.这些是非常好的方法,可以让开发者 ...
- Unity应用架构设计(1)—— MVVM 模式的设计和实施(Part 1)
初识 MVVM 谈起 MVVM 设计模式,可能第一映像你会想到 WPF/Sliverlight,他们提供了的数据绑定(Data Binding),命令(Command)等功能,这让 MVVM 模式得到 ...
- Unity 3D Framework Designing(4)——设计可复用的SubView和SubViewModel(Part 1)
『可复用』这个词相信大家都熟悉,通过『可复用』的组件,可以大大提高软件开发效率. 值得注意的事,当我们设计一个可复用的面向对象组件时,需要保证其独立性,也就是我们熟知的『高内聚,低耦合』原则. 组件化 ...
- Unity 3D Framework Designing(7)——IoC工厂理念先行
一谈到 『IoC』,有经验的程序员马上会联想到控制反转,将创建对象的责任反转给工厂.IoC是依赖注入 『DI』 的核心,大名鼎鼎的Spring框架就是一个非常卓越的的控制反转.依赖注入框架.遗憾的是, ...
- Unity 3D Framework Designing(9)——构建统一的 Repository
谈到 『Repository』 仓储模式,第一映像就是封装了对数据的访问和持久化.Repository 模式的理念核心是定义了一个规范,即接口『Interface』,在这个规范里面定义了访问以及持久化 ...
- Unity 3D Framework Designing(6)——设计动态数据集合ObservableList
什么是 『动态数据集合』 ?简而言之,就是当集合添加.删除项目或者重置时,能提供一种通知机制,告诉UI动态更新界面.有经验的程序员脑海里迸出的第一个词就是 ObservableCollection.没 ...
- Unity应用架构设计(1)—— MVVM 模式的设计和实施(Part 2)
MVVM回顾 经过上一篇文章的介绍,相信你对MVVM的设计思想有所了解.MVVM的核心思想就是解耦,View与ViewModel应该感受不到彼此的存在. View只关心怎样渲染,而ViewModel只 ...
- Unity 3D Framework Designing(2)——使用中介者模式解耦ViewModel之间通信
当你开发一个客户端应用程序的时候,往往一个单页会包含很多子模块,在不同的平台下,这些子模块又被叫成子View(视图),或者子Component(组件).越是复杂的页面,被切割出来的子模块就越多,子模块 ...
随机推荐
- HDU 3782 xxx定律
xxx定律 Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submi ...
- java_JDBC(3)
Batch和Fetch两个特性非常重要.Batch相当于JDBC的写缓冲,Fetch相当于读缓冲 如果把JDBC类比为JAVA IO的话,不使用Fetch和Batch相当于直接使用FileInputS ...
- 如何让 Git 忽略掉文件中的特定行内容?
近期在git遇到几个问题,让我重新认识到git的强大性,下面列出来记录一下 有一个数据库的配置文件,在用 git add 添加到 index file 时不能透露了相关配置.而如果用 .gitigno ...
- vue路由表(简单)
import Vue from 'vue'import VueRouter from 'vue-router'Vue.use(VueRouter) const first = { template: ...
- JavaScript中国象棋程序(7) - 置换表
"JavaScript中国象棋程序" 这一系列教程将带你从头使用JavaScript编写一个中国象棋程序.这是教程的第2节. 这一系列共有9个部分: 0.JavaScript中国象 ...
- ASP.NET Core MVC之Serilog日志处理,你了解多少?
前言 本节我们来看看ASP.NET Core MVC中比较常用的功能,对于导入和导出目前仍在探索中,项目需要自定义列合并,所以事先探索了如何在ASP.NET Core MVC进行导入.导出,更高级的内 ...
- 初识html5——试试博文编辑器
1.html5简介 HTML5 将成为 HTML.XHTML 以及 HTML DOM 的新标准. HTML 的上一个版本诞生于 1999 年.自从那以后,Web 世界已经经历了巨变. HTML5 仍处 ...
- iOS runtime的应用实例
一直想弄明白runtime是怎么回事,因为面试的时候这是一道必备问题,但是平时用的机会真的少之又少,我一度以为runtime只是用来装13的利器,没什么卵用.但是随着学习的增多,发现runtime ...
- samba服务搭建
文件传输 smb主配置文件 /etc/samba/smb.conf? yum install samba samba-client 要启动smb和nmb服务 修改安全级别 security = sha ...
- CSS中padding和margin以及用法
CSS中padding与margin 1.padding:内边距,表示控件内容相对于边缘的距离. 2.margin:外边距,表示控件边缘相对于父空间的边缘. 参考:http://www.studyof ...