首先简单说一下MVP,对于一个UI模块来说,它的所有功能被分割为三个部分,分别通过Model、View和Presenter来承载。Model、View和Presenter相互协作,完成对最初数据的呈现和对用户操作的响应,它们具有各自的职责划分。Model可以看成是模块的业务逻辑和数据的提供者;View专门负责数据可视化的呈现,和用户交互事件的相对应。一般地,View会实现一个相应的接口;Presenter是一般充当Model和View的纽带。在MVP中,应用程序的逻辑主要在Presenter来实现,View是很薄的一层,能够把信息显示清楚即可,View只应该有简单的Set/Get的方法,用户输入和设置界面显示的内容,除此就不应该有更多的内容。

下面就一个简单的例子来看看具体的MVP模式是如何实现的:

假设我们要实现如下功能:

从图中可以看到该模块的功能很简单,用户添加新的User点击Save后系统保存并刷新上方列表。

首先隔离功能,构建两个用户控件UserAdd和UserList

 

这样分开两个用户控件是为了实现功能的分离UserAdd只负责收集用户输入,UserList只负责显示数据列表

 

接着我们看看UserAdd的代码实现:

从代码中可以看到UserAdd的功能非常简单、单一,只负责显示UserName和UserAge,接受了用户的Click事件也不处理而是公开了一个UserAddEvent让外部去实现。

UserAdd
public partial class UserAdd : UserControl, IUserAdd
    {
        public event EventHandler UserAddEvent;

        public string UserName
        {
            set { this.txbName.Text = value; }
            get { return this.txbName.Text; }
        }

        public string UserAge
        {
            set { this.txbAge.Text = value; }
            get { return this.txbAge.Text; }
        }

        public UserAdd()
        {
            InitializeComponent();
        }

        private void btnAdd_Click(object sender, EventArgs e)
        {
           if (UserAddEvent != null) UserAddEvent(this, e);
        }
    }

 

代码中可以看到,UserAdd实现了一个接口IUserAdd

IUserAdd
1 public interface IUserAdd
2     {
3         event EventHandler UserAddEvent;
4 
5         string UserName { get; set; }
6 
7         string UserAge { get; set; }
8 
9     }

抽象出一个接口是为了,以后Presenter依赖抽象即依赖接口而不是依赖特定的UserAdd类。
然后对UserList也可以进行相似的抽象实现。

Model用于维护User列表信息,下面来看一下Model的实现:

UserModel
 1 public class UserModel:IUser
 2     {
 3         private readonly IList<User> _users = new List<User>();
 4 
 5         public UserModel()
 6         {
 7             // generate some test data            
 8             AddItem(new User { Name = "Peter", Age = 29 });
 9         }
10 
11         /// <summary>
12         /// Return data property cast to proper type
13         /// </summary>
14         public IList<User> Users
15         {
16             get { return _users; }
17         }
18 
19         /// <summary>
20         /// add an item to the data
21         /// </summary>
22         /// <param name="user"></param>
23         public void AddItem(User user)
24         {
25             Users.Add(user);
26         }
27 
28         /// <summary>
29         /// update an item in the data
30         /// </summary>
31         /// <param name="user"></param>
32         public void UpdateItem(User user)
33         {
34             for (int i = 0; i < Users.Count; i++)
35             {
36                 if (Users[i].Name.Equals(user.Name))
37                 {
38                     Users[i] = user;
39                     break;
40                 }
41             }
42         }
43 
44         /// <summary>
45         /// delete an item in the data
46         /// </summary>
47         /// <param name="user"></param>
48         public void DeleteItem(User user)
49         {
50             for (int i = 0; i < Users.Count; i++)
51             {
52                 if (Users[i].Name.Equals(user.Name))
53                 {
54                     Users.RemoveAt(i);
55                     break;
56                 }
57             }
58         }
59     }

我们也为UserModel抽象出一个接口:

IUserModel
1     public interface IUser
2     {
3         IList<User> Users { get; }
4         void AddItem(User user);
5         void UpdateItem(User user);
6         void DeleteItem(User user);
7     }

这样做的目的也是为了使Presenter依赖于接口而不是依赖于UserModel类。

好了View和Model都实现好了,下面要实现的是最关键的Presenter了,需要通过Presenter将View和Model联系起来。

先来看UserAddPresenter的实现:

UserAddPresenter
 1 public class UserAddPresenter:IPresenter
 2     {
 3         public IUserAdd _userAdd;
 4         public IUserModel _userModel;
 5         public UserAddPresenter(IUserAdd userAdd, IUserModel userModel)
 6         {
 7             this._userAdd = userAdd;
 8             this._userModel = userModel;
 9             this._userAdd.UserAddEvent += UserAddResponse;
10             //初始化时将此Presenter加入到MessageCenter的PresenterList上
11             MessageCenter.SinlgeInstance.AddPresenter(this);
12         }
13         public void UserAddResponse(object sender, EventArgs e)
14         {
15             _userModel.AddItem(
16                 new User()
17                     {
18                         Name = _userAdd.UserName, Age = Convert.ToInt32(_userAdd.UserAge)
19                     });
20             SendMessage(OperationType.Add);
21         }
22         public void SendMessage(OperationType operationType)
23         {
24 
25             MessageCenter.SinlgeInstance.CallPresenters(operationType);
26             
27         }
28         public void ResponseCall(OperationType operationType)
29         {
30             
31         }
32     }

代码中可以看到UserAddPresenter中将IUserAdd,IUserModel联系起来。
this._userAdd.UserAddEvent += UserAddResponse;

此Presenter中实现了IUserAdd.UserAddEvent也就是说UserAdd接收到了客户的按钮点击 具体的逻辑实现UserAdd就不管了,而是在此交给了UserAddPresenter来处理。

可以看得到UserAddPresenter首先调用了Model将数据保存了起来,随后调用SendMessage发送了一条消息。

现在我们想要用户点击Save时保存用户输入的数据这一步已经实现,如何能实现刷新用户列表呢?我们肯定要有个地方通知到UserList。

我们调用了MessageCenter.SinlgeInstance.CallPresenters那下面我们看看MessageCenter中做了什么。

MessageCenter
 1 public class MessageCenter
 2     {
 3         private MessageCenter()
 4         {
 5             PresentersList = new List<IPresenter>();
 6         }
 7         private static MessageCenter instance;
 8         private static object lockObj=new object();
 9         public static MessageCenter SinlgeInstance
10         {
11             get
12             {
13                 if (instance == null)
14                 {
15                     lock (lockObj)
16                     {
17                         if (instance == null)
18                         {
19                             instance = new MessageCenter();
20                         }
21                     }
22                 }
23                 return instance;
24             }
25         }
26         protected IList<IPresenter> PresentersList
27         {
28             get;
29             set;
30         }
31         public void AddPresenter(IPresenter presenter)
32         {
33             PresentersList.Add(presenter);
34             
35         }
36         public void RemovePresenter(IPresenter presenter)
37         {
38             PresentersList.Remove(presenter);
39         }
40         public void CallPresenters(OperationType operationType)
41         {
42             foreach (IPresenter presenter in PresentersList)
43                 presenter.ResponseCall(operationType);
44         }
45     }

可以看出来MessageCenter中维护了一个PresentersList,当有消息发来时MessageCenter就会遍历PresentersList发送消息,谁需要对这个消息进行处理即自行处理,不需要的话直接忽略即可。
这有点类似于,员工小王用OutLook给公司所有人发了一封关于设计模式培训的邮件,他并不知道"所有人"里都包含哪些人,他只需要在收件人里写上AllUsers,点发送即可具体谁会收到谁想处理已经和小王无关,消息中心收到消息后,会遍历公司所有人并将邮件发给他们,所有人都会收到邮件,然而具体怎么处理是他们自己的事,测试人员可能会直接忽略这封邮件,而开发或设计人员可能会在日程上进行标记以便能准时参加培训。

来看看UserListPresenter的实现吧:

UserListPresenter
 1     public class UserListPresenter : IPresenter
 2     {
 3         public IUserModel _userModel;
 4         public IUserList _userList;
 5         public UserListPresenter(IUserList userList, IUserModel userModel)
 6         {
 7             this._userList = userList;
 8             this._userModel = userModel;
 9             this._userList.StartLoadEvent += UserListLoad;
10             MessageCenter.SinlgeInstance.AddPresenter(this);
11         }
12         public void UserListLoad(object sender, EventArgs e)
13         {
14             _userList.Users = _userModel.Users;
15         }
16         public void SendMessage(OperationType operationType)
17         {
18             MessageCenter.SinlgeInstance.CallPresenters(operationType);
19         }
20         public void ResponseCall(OperationType operationType)
21         {
22             switch (operationType)
23             {
24                 case OperationType.Add:
25                     _userList.Users = _userModel.Users;
26                     break;
27                 default:
28                     break;
29             }
30         }
31     }

可以看到ResponseCall方法中对Add消息进行了处理即刷新列表。 每个Presenter都需要有一个ResponseCall方法,所以抽象出接口IPresenter

IPresenter
1 public interface IPresenter
2     {
3         void ResponseCall(OperationType operationType);
4     }

这样的话在MessageCenter就可以遍历调用IPresenter的ResponseCall方法了。

这样做的好处是,在设计每个Presenter的时候,不必关心它以后会被谁调用,只需要处理好自己的逻辑以及对感兴趣的消息的处理。

至此,这个简单的示例就讲解完毕了。

总结一下MVP,Model负责数据的维护,View只负责显示数据,Presenter就是将View和Model联系起来,而Presenter之间的交互也不是直接交互而是通过MessageCenter进行联系。

采用MVP模式实现的项目很容易进行单元测试,因为处理逻辑主要集中在Presenter中,而Presenter与Model和View的关联也都是通过接口,这样就可以在Model和View都没有完成的情况下对Presenter进行单元测试。

 

本人初识此模式,在此也只是将自己的一些培训所得和肤浅总结记录一下而已,纰漏之处欢迎各位高手指正。

posted on 2012-09-08 12:06  星星小阁阁主  阅读(353)  评论(0编辑  收藏  举报