MVP+WCF+三层结构搭建项目框架(上)

  最近,我一直在重构之前做的一个项目,在这个过程中感慨万千。原先的项目是一个运用了WCF的C/S系统,在客户端运用了MVC模式,但MVC的View、Model耦合以及WCF端分布式欠佳等问题让我有了重构的想法,经过了一段时间的改造,逐渐形成了MVP+三层结构+WCF的面向服务的程序架构。在这里我把我的想法写成了一个例子,供大家参考。

  在正式开始讲解之前,我必须得感谢Artech、代震军等诸多大虾,他们的文章给了我很大的启发。

  我写的这个例子是关于博客管理的,逻辑很简单,就是用户发表文章、发表评论,管理员可以对用户进行管理。让我们先从MVP的运用开始讲起。

  MVPMVC的选择

  关于MVP和MVC,我只谈谈在重构过程中的看法。在经典的MVC中,View会通过Controller调用Model中的方法,Model被更新后会立即通知View,因此View与Model还是存在一定程度的耦合的,Controller也只是作为一个简单的消息分发器,View与Controller也紧紧的贴合好像是一个整体。而在MVP中,Presenter代替Controller而出现,成为View与Model解耦的关键,在MVP中,Presenter掌管大权,View的请求提交给Presenter,由Presenter调用Model,Model更新后的状态再返回给Presenter,由Presenter控制View进行相应的显示。如此,Model与View被完全解耦,View与Controller也形成了单向依赖。MVC与MVP可以说各有特点,但我更倾向于MVP。

  关于MVC和MVP,给大家看这两张图就很明白了。对于刚接触MVP的朋友可能不知道View、Presenter和Model中具体该实现哪些东西,我会在用实例讲解的时候告诉大家我的想法。

 

         

 

 

 

 

 

 

 

 

  

  MVP实战运用

  在确定使用MVP模式之后,我给我的博客程序安排了如下几个项目:

Main:主程序入口点。

Common:存放委托和公共组件等。

Model:MVP中的M,注意区别于三层中的Model。

Presenter:MVP中的P。

View:MVP中的V。

DTO:这个项目其实和三层中的Model作用是一样的,但是我为了区别于MVP中的Model,把它叫做DTO,其实它的作用就是一个DTO。

  项目之间的引用关系是这样的:

  Main直接调用Presenter启动程序。Presenter与View,Presenter和Model分别都是单向引用,而View和Model完全没有任何联系。View和Presenter需要引用Common,使用其中的一些公共组件,而DTO作为存放数据传输对象的项目View、Presenter和Model必须都要引用。

  在搭建好项目框架之后,我们可以先从Model入手,因为Model是业务逻辑的所在地,是数据的提供者,它完全基于用例的。按照本例需求,分析出三个实体,分别是User(用户),Note(文章),Comment(评论),我们就以User为例,写一个Model。代码如下所示:

View Code
 1     /// <summary>
2 /// The interface of user model
3 /// </summary>
4 public interface IUserGroup
5 {
6 #region --Methods--
7 /// <summary>
8 /// Get all users
9 /// </summary>
10 /// <returns>Users</returns>
11 IList<User> GetAllUsers();
12
13 /// <summary>
14 /// Get user by user id
15 /// </summary>
16 /// <param name="id">the user id</param>
17 /// <returns>User</returns>
18 User GetUserById(string id);
19
20 /// <summary>
21 /// Update user
22 /// </summary>
23 /// <param name="user">the user</param>
24 void UpdateUser(User user);
25
26 /// <summary>
27 /// Delete user by user id
28 /// </summary>
29 /// <param name="userId">the user id</param>
30 void DeleteUser(string userId);
31 #endregion
32 }

 

  这是一个用于处理User业务逻辑的接口,其中使用到的User类就是DTO中的User实体。Model中把接口公开出来供Presenter调用就可以了,Note和Comment的代码也类似,P-M之间的交互还是比较简单的。

  OK,设计完Model,我们再来看看界面如何呈现,我的想法如图所示:

  界面很丑陋,大家就凑合看吧。窗口就一个,有一个DataGridView用来显示所有用户,当选中一行时在下面的TextBox中显示用户的详细信息。我们所能看到的这个UI界面就是MVP中View了。设计View时需要注意的是,View一定要针对接口设计,不要针对实现,因为我们的设想是将View做成Passive View,一定要让Presenter依赖于一个抽象的View。

  在本例中,我将这个界面分解为两个View,一个是显示用户详细信息的主窗体(IUserControlView),一个是显示所有用户信息的列表(IGridView),可以看到IGridView是IUserControlView的一部分。代码如下所示:  

IUserControlView
 1     /// <summary>
2 /// The interface of UserControlView
3 /// </summary>
4 public interface IUserControlView
5 {
6 #region --Event--
7 /// <summary>
8 /// Occurs when the view was loaded
9 /// </summary>
10 event EventHandler OnViewLoad;
11 #endregion
12
13 /// <summary>
14 /// set the UserGridView
15 /// </summary>
16 IGridView UserGridView
17 {
18 set;
19 }
20
21 #region --Methods--
22 /// <summary>
23 /// Initialize the components of this view
24 /// </summary>
25 void Initialize();
26
27 /// <summary>
28 /// Show one user information at the interface
29 /// </summary>
30 /// <param name="user"></param>
31 void ShowUserInfo(User user);
32
33 /// <summary>
34 /// Show alert form
35 /// </summary>
36 /// <param name="message">Messages should be shown</param>
37 void Alert(string message);
38 #endregion
39 }
IGridView
 1     /// <summary>
2 /// The interface of Grid View
3 /// </summary>
4 public interface IGridView
5 {
6 #region --Event--
7 /// <summary>
8 /// Occurs when a user was selected
9 /// </summary>
10 event UserEventHandler OnUserSelected;
11 /// <summary>
12 /// Occurs when a user is begin to be edited
13 /// </summary>
14 event UserEventHandler OnUserBeginEdit;
15 #endregion
16
17 #region --Properties--
18 /// <summary>
19 /// 设置此View相对于父View的位置
20 /// </summary>
21 Point ViewLocation
22 {
23 set;
24 }
25 #endregion
26
27 #region --Methods--
28 /// <summary>
29 /// bind data to this grid
30 /// </summary>
31 void BindData(IList<User> users);
32 #endregion
33 }

  实现代码如下:

View Code
1 public class UserGridView : DataGridView, IGridView
2 public class UserDetailForm : Form, IUserControlView

  View实现代码的细节我就不贴了,在文章最后会提供示例的下载链接。

  需要注意的是,View既然是针对抽象设计,接口中就不能暴露任何UI实现的细节

  现在,再来看看Presenter如何设计,一般来说一个View就有一个相对应的Presenter来控制,Presenter代码如下所示:

IGridPresenter
 1     /// <summary>
2 /// The interface of GridPresenter
3 /// </summary>
4 public interface IGridPresenter
5 {
6 #region --Event--
7 /// <summary>
8 /// Occurs when a user was selected
9 /// </summary>
10 event UserEventHandler OnUserSelected;
11 /// <summary>
12 /// Occurs when a user is begin to be edited
13 /// </summary>
14 event UserEventHandler OnUserBeginEdit;
15 #endregion
16
17 #region --Properties--
18 /// <summary>
19 /// Get the view
20 /// </summary>
21 IGridView View
22 {
23 get;
24 }
25 #endregion
26
27 #region --Methods--
28 /// <summary>
29 /// Show a group users data
30 /// </summary>
31 /// <param name="users"></param>
32 void ShowUsers(IList<User> users);
33 #endregion
34 }

 

IUserControlPresenter
 1     /// <summary>
2 /// The interface of UserControlPresenter
3 /// </summary>
4 public interface IUserControlPresenter
5 {
6 /// <summary>
7 /// Show the mian view.Start the application
8 /// </summary>
9 void Run();
10 }

 

  Presenter对View的引用是单向的,View不知道哪个Presenter在用它,View也无法访问到Presenter。我们让Presenter订阅View中的事件以响应View的请求。View是被动的,需要由Presenter控制,因此在Presenter实例化的时候同时实例化相应的View。

  IGridPresenter的实现代码如下所示:

IGridPresenter Realize
  1     public class UserGridPresenter : IGridPresenter
2 {
3 #region --Event--
4 /// <summary>
5 /// Occurs when a user was selected
6 /// </summary>
7 public event UserEventHandler OnUserSelected;
8 /// <summary>
9 /// Occurs when a user is begin to be edited
10 /// </summary>
11 public event UserEventHandler OnUserBeginEdit;
12 #endregion
13
14 #region --Fields--
15 private IGridView mView;
16 #endregion
17
18 #region --Properties--
19 /// <summary>
20 /// Get the GridView
21 /// </summary>
22 public IGridView View
23 {
24 get { return mView; }
25 }
26 #endregion
27
28 #region --Constructor--
29 /// <summary>
30 /// Default constructor
31 /// </summary>
32 public UserGridPresenter()
33 {
34 mView = new UserGridView();
35 AttachToUserGridView(mView);
36 }
37 #endregion
38
39 #region --Public Methods--
40 /// <summary>
41 /// show user data
42 /// </summary>
43 /// <param name="users"></param>
44 public void ShowUsers(IList<User> users)
45 {
46 mView.BindData(users);
47 }
48 #endregion
49
50 #region --Private Methods--
51 /// <summary>
52 /// Attach to the UserGridView
53 /// </summary>
54 /// <param name="view"></param>
55 private void AttachToUserGridView(IGridView view)
56 {
57 if (view != null)
58 {
59 view.OnUserSelected += new UserEventHandler(UserGridView_OnUserSelected);
60 view.OnUserBeginEdit += new UserEventHandler(UserGridView_OnUserBeginEdit);
61 }
62 }
63 #endregion
64
65 #region --Event Methods--
66 /// <summary>
67 /// Occurs when the OnUserSelected event in UserGridView was raised
68 /// </summary>
69 private void UserGridView_OnUserSelected(object sender, UserEventArgs e)
70 {
71 RaiseOnUserSelected(e.UserValue);
72 }
73
74 /// <summary>
75 /// Occurs when the OnUserBeginEdit event in UserGridView was raised
76 /// </summary>
77 private void UserGridView_OnUserBeginEdit(object sender, UserEventArgs e)
78 {
79 RaiseOnUserBeginEdit(e.UserValue);
80 }
81 #endregion
82
83 #region --Raise Event Methods--
84 /// <summary>
85 /// Raise the OnUserSelected event
86 /// </summary>
87 /// <param name="user"></param>
88 private void RaiseOnUserSelected(User user)
89 {
90 UserEventHandler handler = OnUserSelected;
91 if (handler != null)
92 {
93 UserEventArgs e = new UserEventArgs();
94 e.UserValue = user;
95 handler(this, e);
96 }
97 }
98
99 /// <summary>
100 /// Raise the OnUserBeginEdit event
101 /// </summary>
102 /// <param name="user"></param>
103 private void RaiseOnUserBeginEdit(User user)
104 {
105 UserEventHandler handler = OnUserBeginEdit;
106 if (handler != null)
107 {
108 UserEventArgs e = new UserEventArgs();
109 e.UserValue = user;
110 handler(this, e);
111 }
112 }
113 #endregion
114 }

 

  IUserControlPresenter的实现代码如下所示:

IUserControlPresenter Realize
  1    public class UserControlPresenterBase : IUserControlPresenter
2 {
3 #region --Fields--
4 private IGridPresenter mGridPresenter;
5 private IUserControlView mUserControlView;
6 #endregion
7
8 #region --Constructor--
9 /// <summary>
10 /// Default constructor
11 /// </summary>
12 public UserControlPresenterBase()
13 {
14 Initialize();
15 }
16 #endregion
17
18 #region --Public Methods--
19 /// <summary>
20 /// Show the mian view.Start the application
21 /// </summary>
22 public void Run()
23 {
24 ServiceCollection.LoadServer();
25 //Run as main form
26 Application.Run(mUserControlView as UserDetailForm);
27 }
28 #endregion
29
30 #region --Private Methods--
31 /// <summary>
32 /// Initialize this presenter
33 /// </summary>
34 private void Initialize()
35 {
36 mUserControlView = new UserDetailForm();
37 mGridPresenter = new UserGridPresenter();
38 mUserControlView.UserGridView = mGridPresenter.View;
39 //The UI initialize method should be executed until all sub views was assigned
40 mUserControlView.Initialize();
41 AttachToUserControlView(mUserControlView);
42 AttachToUserGridPresenter(mGridPresenter);
43 }
44
45 /// <summary>
46 /// Attach to the UserControlView
47 /// </summary>
48 /// <param name="view"></param>
49 private void AttachToUserControlView(IUserControlView view)
50 {
51 if (view != null)
52 {
53 view.OnViewLoad += new EventHandler(UserControlView_OnViewLoad);
54 }
55 }
56
57 /// <summary>
58 /// Attach to the UserGridPresenter
59 /// </summary>
60 /// <param name="presenter"></param>
61 private void AttachToUserGridPresenter(IGridPresenter presenter)
62 {
63 if (presenter != null)
64 {
65 presenter.OnUserSelected += new UserEventHandler(UserGridPresenter_OnUserSelected);
66 presenter.OnUserBeginEdit += new UserEventHandler(UserGridPresenter_OnUserBeginEdit);
67 }
68 }
69
70 /// <summary>
71 /// Show the view to edit a user
72 /// </summary>
73 /// <param name="user"></param>
74 private void ShowEditUserView(User user)
75 {
76 IEditUserPresenter presenter = new EditUserPresenter();
77 presenter.OnUserEdited += new UserEventHandler(EditUserPresenter_OnUserEdited);
78 presenter.ShowView(user);
79 }
80
81 /// <summary>
82 /// Show all users
83 /// </summary>
84 private void ShowAllUsers()
85 {
86 IWS_Blog blogService = ServiceCollection.BlogService;
87 IList<User> userList = blogService.GetAllUsers();
88 mGridPresenter.ShowUsers(userList);
89 }
90 #endregion
91
92 #region --Event Methods--
93 /// <summary>
94 /// Occurs when the UserControlView was loaded
95 /// </summary>
96 private void UserControlView_OnViewLoad(object sender, EventArgs e)
97 {
98 ShowAllUsers();
99 }
100
101 /// <summary>
102 /// Occurs when the OnUserSelected event in UserGridPresenter was raised
103 /// </summary>
104 private void UserGridPresenter_OnUserSelected(object sender, UserEventArgs e)
105 {
106 mUserControlView.ShowUserInfo(e.UserValue);
107 }
108
109 /// <summary>
110 /// Occurs when the OnUserBeginEdit event in UserGridPresenter was raised
111 /// </summary>
112 private void UserGridPresenter_OnUserBeginEdit(object sender, UserEventArgs e)
113 {
114 ShowEditUserView(e.UserValue);
115 }
116
117 /// <summary>
118 /// Occurs when a user edit finished
119 /// </summary>
120 private void EditUserPresenter_OnUserEdited(object sender, UserEventArgs e)
121 {
122 ShowAllUsers();
123 }
124 #endregion
125 }

 

  初始化加载所有用户信息的流程是这样的:当UserControlView加载时,UserControlPresenter随之响应并调用UserModel中的GetAllUsers方法。UserControlPresenter获取到数据后,再调用GridPresenter中的ShowUsers方法,将查询的数据绑定到GridView上,这时便看到了数据。在这个过程中,View提交请求是通过事件响应实现的,只要涉及到与数据相关的请求都必须要提交到Presenter中,由Presenter来决定下一步该做什么,View中则不能包含任何与数据相关的业务逻辑。从这个流程中我们也可以看到Presenter的掌控地位,他属于指手划脚的那类人,只是在告诉别人需要做什么,但自己却不会亲自动手。

  大家可能注意到了,我把UserControlView中的Initialize方法公开在接口中,并没有在UserControlView构造时执行,这是因为UserControlView依赖于一个抽象的GridView,在构造时GridView还没有被注入,显然在这个时候初始化会出现未将对象引用设置到对象的实例的异常。因此我把UserControlView的初始化工作交给Presenter处理,将初始化的时机延后到所有依赖注入完成,而View不会主动执行Initialize。

  IUserControlPresenter已经是最顶层的Presenter了,它只需要公开出接口供Main调用启动程序即可。

  我感觉,P-V交互是MVP模式运用的重点,本人水平有限,文中疏漏或讲解不到位的地方还请大家谅解。关于MVP,大家可以看看Artech和代震军的文章,你们会学到很多,还有一篇关于MVP的14条规则的文章也强烈推荐。

  这篇文章是关于MVP运用的,下面我会介绍我是如何把WCF服务端加入进来的,以及我对三层结构运用的心得。

   MVP+WCF+三层结构搭建项目框架(下)

posted @ 2012-03-19 08:47  Charles Yan  阅读(5008)  评论(11编辑  收藏  举报