在.NET中,对于ASP.NET,有MVC;对于WPF、SILVERLIGHT,有MVVM。然而在桌面开发领域,似乎微软并没有推出什么强力的框架。但笔者在写程序的时候很不喜欢把代码全部混杂在一个类中。这个问题很容易解决,一种是使用现成的对平台没有依赖性的MVC框架,比如PureMVC,当然学习一个框架需要一些时间,另一种方法就是自己做一个小框架,恐怕称之为框架都有些太夸大了。
首先需要确定的是这个小框架要实现的功能。MVC虽然经典,但是View层的数据获取需要从Model直接获取,而View的操作行为则是需要通过Controller来更新Model。也就是说在View与Model通信过程中,Controller负责那些变更状态的事情。然而MVC中比较严重的问题是View需要引用Model,这就导致了View层对Model的依赖。主动MVC与被动MVC都存在这样的问题。(见Figure 1 主动MVC、Figure 2 被动MVC)而MVP则不存在这样的问题,但是在MVP中Presenter承担了更多的事情。在Presenter中,大致有两种信息,一种是改变Model状态的控制信息,一种是改变View显示的状态信息。对于特定的策略,如Presenter是主动的询问Model发现变更后通知View,还是Model通过Observer模式通知Presenter,Presenter再去通知View改变内容这类的事情,则可以具体到每个特定的Presenter中来实现。(见Figure 3 MVP模式)
Figure 1 主动MVC
Figure 2 被动MVC
Figure 3 MVP模式
MVP具体到.NET中来,在其中使用的Observer模式自然可以用事件实现。笔者认为虽然一个Presenter有可能会存在需要多个视图以及多个Model的情况,但是大多情况下,一个Presenter仅仅关注一个Model。对于View,情况则比较复杂,通常为了将某一类Model的信息显示出来,我们会为其专门定制一个View,但是,我们还需要输出许多信息,看上去我们是需要向其他的View发送信息。但是,如果我们为每一个View都做一个Presenter,一个Model的话,我们只需要在当前Presenter引用那个View所对应的Model,发送相应信息就好了。所以,笔者的原则就是,不要让更新不属于当前Presenter的View,而是通过该Presenter引用Model实现其他信息的输出。
Figure 4 框架接口
01 |
public interface IView |
02 |
{ |
03 |
void Initialize(); |
04 |
} |
05 |
public interface IModel |
06 |
{ |
07 |
void Initialize(); |
08 |
event EventHandler Initialized; |
09 |
} |
10 |
public interface IPresenter |
11 |
{ |
12 |
void SetView(IView view); |
13 |
void SetModel(IModel model); |
14 |
void SetUnityContainer(IUnityContainer unityContainer); |
15 |
void Initialize(); |
16 |
string Name { get ; } |
17 |
Guid ID { get ; } |
18 |
IView GetView(); |
19 |
IModel GetModel(); |
20 |
} |
21 |
public interface IPresenter<V, M> : IPresenter |
22 |
where V : IView |
23 |
where M : IModel |
24 |
{ |
25 |
void SetView(V view); |
26 |
void SetModel(M model); |
27 |
} |
以上是笔者设计的接口。Model与View的初始化有时需要一定的时间,我们可以把这些方法封装起来,便于今后控制。然后在Presenter中提供了一些基本方法,如更改获取View及Model,以及一个初始化方法,该初始化方法的默认实现是会初始化View后初始化Model。
在设计过程中,笔者认为有大量的Presenter方法是需要重用的,因此就写了一个抽象的基类实现一些方法,在开发过程中,又引入了IUnityContainer,因此实现了两个版本。BasePresenter不会自动注入View与Model,而Presenter会自动注入。
Figure 5 Presenter基类
在实现BasePresenter的过程中,使用了模板方法将几个最常用的方法定义出来,如在添加Model的时候绑定Model的事件,移除Model的时候移除绑定Model的事件。还有就是在Model初始化完成的时候,也经常需要Presenter去做一些事情,也定义了出来。
为了便于标识与更友好的显示名称,也加入了相关的属性来标识。
001 |
public abstract class BasePresenter<V, M> : IPresenter<V, M> |
002 |
where V : IView |
003 |
where M : IModel |
004 |
{ |
005 |
|
006 |
public BasePresenter() |
007 |
{ |
008 |
var type = this .GetType(); |
009 |
var assembly = type.Assembly; |
010 |
try |
011 |
{ |
012 |
ResourceManager rm = new ResourceManager(assembly.GetName().Name + ".TextResource" , assembly); |
013 |
|
014 |
Name = rm.GetString(type.Name); |
015 |
} |
016 |
catch |
017 |
{ |
018 |
} |
019 |
finally |
020 |
{ |
021 |
if (Name == null ) |
022 |
Name = type.Name; |
023 |
} |
024 |
ID = Guid.NewGuid(); |
025 |
} |
026 |
public IView GetView() |
027 |
{ |
028 |
return this .View; |
029 |
} |
030 |
public IModel GetModel() |
031 |
{ |
032 |
return this .Model; |
033 |
} |
034 |
public V View { get ; private set ; } |
035 |
public M Model { get ; private set ; } |
036 |
public string Name { get ; protected set ; } |
037 |
public Guid ID { get ; protected set ; } |
038 |
protected IUnityContainer UnityContainer { get ; private set ; } |
039 |
public delegate void ThreadInvoker(); |
040 |
|
041 |
protected virtual void Invoke(ThreadInvoker invoker) |
042 |
{ |
043 |
invoker.BeginInvoke( null , null ); |
044 |
} |
045 |
/// <summary> |
046 |
/// Remove the model events bound to the Presenter |
047 |
/// Note:The Initialized event. |
048 |
/// </summary> |
049 |
protected abstract void BindModelEvents(); |
050 |
|
051 |
/// <summary> |
052 |
/// Remove the model events bound to the Presenter |
053 |
/// Note:The Initialized event. |
054 |
/// </summary> |
055 |
protected abstract void RemoveModelEvents(); |
056 |
|
057 |
|
058 |
#region IPresenter<V,M> 成员 |
059 |
|
060 |
public virtual void SetView(V view) |
061 |
{ |
062 |
this .View = view; |
063 |
} |
064 |
|
065 |
public virtual void SetModel(M model) |
066 |
{ |
067 |
if ( this .Model != null ) |
068 |
{ |
069 |
Model.Initialized -= new EventHandler(ModelInitialized); |
070 |
RemoveModelEvents(); |
071 |
} |
072 |
this .Model = model; |
073 |
Model.Initialized += new EventHandler(ModelInitialized); |
074 |
BindModelEvents(); |
075 |
} |
076 |
|
077 |
public void SetView(IView view) |
078 |
{ |
079 |
SetView((V)view); |
080 |
} |
081 |
|
082 |
public void SetModel(IModel model) |
083 |
{ |
084 |
SetModel((M)model); |
085 |
} |
086 |
|
087 |
public virtual void Initialize() |
088 |
{ |
089 |
View.Initialize(); |
090 |
Model.Initialize(); |
091 |
} |
092 |
|
093 |
|
094 |
/// <summary> |
095 |
/// |
096 |
/// </summary> |
097 |
/// <param name="sender"></param> |
098 |
/// <param name="e"></param> |
099 |
protected abstract void ModelInitialized( object sender, EventArgs e); |
100 |
|
101 |
[InjectionMethod] |
102 |
public void SetUnityContainer(IUnityContainer unityContainer) |
103 |
{ |
104 |
this .UnityContainer = unityContainer; |
105 |
} |
106 |
|
107 |
#endregion |
108 |
} |
通过一些这样的定义,我们就已经实现了一个简易的MVP框架。下面来说说这个框架怎么用。
这个框架比较好的一点就是比较适合懒人用,因为消息通知都是基于.net的方法或者事件来实现的,所以IDE都能直接认。
我们现在要实现一个累加器,Model的实现很容易。有一个方法来进行累加,有一个属性来提供当前数字,还有一个事件来通知累加的数已经改变了。
1 |
public interface IHelloMvpModel : IModel |
2 |
{ |
3 |
void Plus(); |
4 |
int GetNumber { get ; } |
5 |
event EventHandler NumberChanged; |
6 |
} |
01 |
public class HelloMvpModel : IHelloMvpModel |
02 |
{ |
03 |
private int _Number; |
04 |
public void Plus() |
05 |
{ |
06 |
++_Number; |
07 |
if (NumberChanged != null ) |
08 |
NumberChanged( this , EventArgs.Empty); |
09 |
} |
10 |
11 |
public int GetNumber |
12 |
{ |
13 |
get { return _Number; } |
14 |
} |
15 |
16 |
public void Initialize() |
17 |
{ |
18 |
if (Initialized != null ) |
19 |
Initialized( this , EventArgs.Empty); |
20 |
} |
21 |
22 |
public event EventHandler Initialized; |
23 |
24 |
25 |
public event EventHandler NumberChanged; |
26 |
} |
而View层就更简单了,接收用户操作与显示数据。
1 |
public interface IHelloMvpView : IView |
2 |
{ |
3 |
void SetNumber( int number); |
4 |
event EventHandler ChangeNumberRequested; |
5 |
} |
01 |
public partial class HelloMvpView : Form,IHelloMvpView |
02 |
{ |
03 |
public HelloMvpView() |
04 |
{ |
05 |
InitializeComponent(); |
06 |
} |
07 |
|
08 |
public void SetNumber( int number) |
09 |
{ |
10 |
Invoke((MethodInvoker) delegate () |
11 |
{ |
12 |
this .Text = number.ToString(); |
13 |
}); |
14 |
//throw new NotImplementedException(); |
15 |
} |
16 |
|
17 |
public void Initialize() |
18 |
{ |
19 |
|
20 |
//throw new NotImplementedException(); |
21 |
} |
22 |
|
23 |
|
24 |
public event EventHandler ChangeNumberRequested; |
25 |
|
26 |
private void button1_Click( object sender, EventArgs e) |
27 |
{ |
28 |
if (ChangeNumberRequested != null ) |
29 |
ChangeNumberRequested( this , EventArgs.Empty); |
30 |
} |
31 |
} |
在主程序,我们这样来进行调用。
01 |
static void Main() |
02 |
{ |
03 |
Application.EnableVisualStyles(); |
04 |
Application.SetCompatibleTextRenderingDefault( false ); |
05 |
06 |
IUnityContainer container = new UnityContainer(); |
07 |
container.RegisterInstance(container); |
08 |
container.RegisterType<IHelloMvpModel, HelloMvpModel>(); |
09 |
container.RegisterType<IHelloMvpView, HelloMvpView>(); |
10 |
11 |
var p = container.Resolve<HelloMvpPresenter>(); |
12 |
p.Initialize(); |
13 |
Application.Run((Form) p.View); |
14 |
} |
Figure 6 运行截图
Figure 7 点击按钮后
虽然实现起来比较繁杂,但是对比较大的程序来说,有这样一套框架可以很好的帮助开发者,使得混杂在一起的数据更加的清晰。笔者现在已经通过这样的框架实现了一些实用的程序。
这篇文章大概就完了,可能有时间会再写一篇用该框架实现一些通用ToolWindow的实现,在程序源码中的Loning.MvpWinform项目中。写这个小框架大概一年前写的,现在想来IPresenter似乎没什么意义,大概也就是在初始化的时候多态一下,对View与Model的单一限制似乎也不好,在开发过程中有时也会感到很麻烦。但是比较可以肯定的是开发的时候,思维会比较清晰。如果大家有时间看这篇文章,欢迎指出不足之处。
程序源码: http://www.wiisio.com/LoningLibrary.7z
参考文章:MVP模式与MVC模式 http://www.uml.org.cn/sjms/201006244.asp 部分图摘自本篇文章
作者:马昊伯