初识 MVVM

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

MVVM 设计模式顾名思义,通过分离关注点,各司其职。通过 Data Binding 可达到数据的双向绑定,而命令 Command 更是将传统的 Code Behind 事件独立到 ViewModel 中。

MVVM 设计模式在 WPF 中的实现

在WPF中,你会像如下这样去定义一个专门管理视图 View 的 ViewModel:

public class SongViewModel : INotifyPropertyChanged
{
#region Construction
/// Constructs the default instance of a SongViewModel
public SongViewModel()
{
_song = new Song { ArtistName = "Unknown", SongTitle = "Unknown" };
}
#endregion #region Members
Song _song;
#endregion #region Properties
public Song Song
{
get
{
return _song;
}
set
{
_song = value;
}
} public string ArtistName
{
get { return Song.ArtistName; }
set
{
if (Song.ArtistName != value)
{
Song.ArtistName = value;
RaisePropertyChanged("ArtistName");
}
}
}
#endregion #region INotifyPropertyChanged Members public event PropertyChangedEventHandler PropertyChanged; #endregion #region Methods private void RaisePropertyChanged(string propertyName)
{
// take a copy to prevent thread issues
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}

同时在 View 中你需要使用 Binding 将 ViewModel 的属性绑定和控件的内容相绑定:

 <TextBox Content="{Binding ArtistName}" />

值得注意的是,要实现 View 和 ViewModel 双向绑定,我们的 ViewModel 必须实现 INotifyPropertyChanged 接口,由于 WPF Framework 让控件监听了 PropertyChanged 事件,当属性值发生时,触发 PropertyChanged 事件,所以控件就能自动获取到最新的值。反之,当控件的值发生改变时,例如 TextBox 触发 OnTextChanged 事件,自动将最新的值同步到 ViewModel 相应的属性中。

MVP & MVVM

Unity 3D 与 WPF/Sliverlight 不同,它没有提供类似的 Data Binding,也没有像 XAML 一样的视图语法,那么怎样才能在 Unity 3D 中去实现 MVVM 呢?

在 ASP.NET WebForm 时代,那时还没有 ASP.Net MVC 。我们为了让 UI 表现层分离,常常会使用 MVP 设计模式,以下是我在几年前画的一张老图:

MVP 设计模式核心就是,通过定义一个 View,将 UI 抽象出来,它不必关心数据的具体来源,也不必关心点击按钮之后业务逻辑的实现,它只关注 UI 交互。这就是典型的分离关注点。

其实这就是我今天想讲的主题,既然 Unity 3D 没有提供数据绑定,那么我们也可以参考之前 MVP 的设计理念:

将 UI 抽象成独立的一个个 View,将面向 Component 开发转换为面向 View 开发,每一个 View 都有独立的 ViewModel 进行管理,如下所示:

由于 Unity 3D 没有 XAML,也没有 Data Binding 技术,故只能在抽象出来的 View 中去实现类似于 WPF 的 Data Binding,Converter,Command 等。

值得注意的是,MVP 设计模式中数据的绑定是通过将具体的 View 实例传递到 Presenter 中完成的,而 MVVM 是以数据改变引发的事件中完成数据更新的。

MVVM 设计模式在 Unity 3D 中的设计与实现

再回顾一下 WPF 中 ViewModel 的写法。 ViewModel 提供了 View 需要的数据,并且 ViewModel 实现 INotifyPropertyChanged 接口 ,当数据更改时,触发了 PropertyChanged 事件,由于控件也监听了此事件,在事件的响应函数里实现数据的更新。

了解了之后,我们要考虑怎样在 Unity 3D 中去实现它。假设我们需要完成如下的一个功能,并且是使用 MVVM 设计思想实现:

首先,我们要定义一个 View,这个 View 是对 UI 元素的一个抽象,到底要抽象哪些 UI 元素呢?就这个例子而言,InputField,Label,Slider,Toggle,Button 是需要被抽象出来的。

public class SetupView
{
public InputField nameInputField;
public Text nameMessageText; public InputField jobInputField;
public Text jobMessageText; public InputField atkInputField;
public Text atkMessageText; public Slider successRateSlider;
public Text successRateMessageText; public Toggle joinToggle;
public Button joinInButton;
public Button waitButton;
}

可以看到,这是一个很简单的 View。接着我们需要定义一个专门用来管理 View 的 ViewModel,它以属性的形式提供数据,以方法的形式提供行为。

值得注意的是,ViewModel 中的属性不是特殊的属性,它必须具备当数据更改时通知订阅者这个功能,怎么通知订阅者?当然是事件,故我把此属性称为 BindableProperty 属性。

 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);
}
} public override string ToString()
{
return (Value != null ? Value.ToString() : "null");
}
}

接着,我们再定义一个 ViewModel,它为 View 提供了数据和行为:

 public class SetupViewModel : ViewModel
{
public BindableProperty<string> Name = new BindableProperty<string>();
public BindableProperty<string> Job = new BindableProperty<string>();
public BindableProperty<int> ATK = new BindableProperty<int>();
public BindableProperty<float> SuccessRate = new BindableProperty<float>();
public BindableProperty<State> State = new BindableProperty<State>();
}

有了 View 与 ViewModel 之后,我们需要考虑:

  • 怎样为 View 指定一个 ViewModel
  • 当 ViewModel 属性值改变时,怎样订阅触发的 OnValueChanged 事件,从而达到 View 的数据更新

基于以上两点,我们可以定义一个通用的 View,将它命名为 UnityGuiView

public interface IView
{
ViewModel BindingContext { get; set; }
} public class UnityGuiView:MonoBehaviour,IView
{
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;
} }
  • 上述代码中,提供一个 BindingContext 上下文属性,类似于 WPF 中的 DataContext。 BindingContext 属性我们不能将它视为一个简单的属性 ,它是上述定义过的 BindableProperty 类型属性。那么当为一个 View 的 BindingContext 指定 ViewModel 实例时,初始化时,势必会触发 OnValueChanged 事件。

  • 在响应函数 OnBindingContextChanged 中 ,我们可以在此对 ViewModel 中事件进行监听,从而达到数据的更新。当然这是一个虚方法,你需要在子类 View 中 Override。

所以修改定义过的 SetupView,继承自 UnityGuiView:

public class SetupView:UnityGuiView
{
...省略部分代码 public SetupViewModel ViewModel { get { return (SetupViewModel)BindingContext; } } 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;
...
}
UpdateControls();
} private void NameValueChanged(string oldvalue, string newvalue)
{
nameMessageText.text = newvalue.ToString();
}
}

由于子类 Override 了 OnBindingContextChanged 方法,故它会对 ViewModel 的属性值改变事件进行监听,当触发时,将最新的数据同步到 UI 中。

同理,考虑到双向绑定,你也可以在 View 中定义一个 OnTextBoxValueChanged 响应函数,当文本框中的数据改变时,在响应函数中就数据同步到 ViewModel 中。在这我就不累述了。

最后,在 Unity 3D 中将 SetupView 附加到 相应的 GameObject上:

最后在摄像机上加一段脚本,很简单,传入 SetupView 对象并为其绑定 ViewModel:

public SetupView setupView;
void Start()
{
//绑定上下文
setupView.BindingContext=new SetupViewModel();
}

小结

这是一个非常简单的 MVVM 框架,也证明了在 Unity 3D 中实现 MVVM 设计模式的可能性。

源代码托管在Github上,点击此了解

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

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

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

  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. Java中boolean类型到底占用多少字节

    虽然 Java 虚拟机定义了 boolean 这种数据类型,但是只对它提供了非常有限的支持.在 Java 虚拟机中没有任何供 boolean 值专用的字节码指令,在 Java 语言之中涉及到 bool ...

  2. 怎样在chrome中内置扩展,以及一些简单的定制

    本文中chromium 版本为54 参考资料: chromium如何新增extension API以及添加内部扩展 http://slides.com/luyuan/grit#/ 一.嵌入扩展 1.  ...

  3. NSMutableAttributedString

    开发过程中,经常会遇到动态计算行高的问题, - (CGRect)boundingRectWithSize:(CGSize)size options:(NSStringDrawingOptions)op ...

  4. IOS9.0 之后友盟分享详细过程

    一: 申请友盟的AppKey(友盟的Key是根据应用的名称生成的!) 在友盟注册了你自己的开发者账号后就可以申请AppKey了.然后在这个方法里面设置Key - (BOOL)application:( ...

  5. SUI Mobile

    <header class="bar bar-nav"> <h1 class='title'>只有图标的表单</h1> </header& ...

  6. 一个技术汪的开源梦 —— 微信开发工具包(WeixinSDK)

    由于春节的关系 WeixinSDK 这个开源项目的进展比预期推迟了大约一个月的时间,值得高兴的是到目前为止该项目的重要模块已经开发完毕.  - 关于项目 该项目的背景是现在微信公众号.微信服务号乃至微 ...

  7. vim编译安装+lua模块

    vim编译安装+lua模块 使用背景:代码自动补全插件,需要安装lua模块 安装准备,首先下载安装vim所依赖的其它安装包,ncurses,lua,readline,vim 源码下载,编译安装 ncu ...

  8. jquery序列化form表单

    在开发中有时需要在js中提交form表单数据,就需要将form表单进行序列化. jquery提供的serialize方法能够实现. $("#searchForm").seriali ...

  9. 将Error异常日志从普通日志中剥离

    开发过程中经常需要调试和线上环境查看异常日志的需求,但普通消息与异常消息混在一起实在是非常难得找,上则NM的文档够你头痛,所以就将Error级别的日志抽离出来. 本示例采用log4net来配置: 1. ...

  10. hibernate系列笔记(2)---Hibernate的核心API

    Hibernate的核心API 一般我们通过hibernate进行操作的时候,都会遵循下面的流程,那么接下来我对每一个步骤进行讲解: 1 public void testInsert() { 2 // ...