Model View Presenter (MVP) design pattern close look – Part 2 – Passive View

What is Model View Presenter?

MVP pattern is one of the major patterns used for extracting business logic outside of UI elements and by that, enabling unit testing the UI without the need for using specific UI based testing tools

If you are new to Model View Presenter pattern at all, you should check the Model View Presenter entry level post which explains the most important concepts of wiring the presenter and view in step by step manner. Once you would chew that, come back to this post and continue reading :)

  As we can see from this diagram:

  • View contains the Presenter instance (view "knows" presenter)
  • Presenter is the only class knowing how to reach to model and retrieve the data needed for performing business logic
  • Presenter talks to the View through the view interface (abstracted representation of the View without UI specific attributes)
  • View doesn't know nothing about the Model

View responsibility is to show the data provided by presenter, and purpose of the presenter is to reach to model, retrieve the needed data, performs required processing and returns the UI prepared data to the view.

In July 2006, two year old Model View Presenter pattern has been retired and two variations from original MVP pattern came up:

  1. Supervising controller - where the view is handling UI operations on his own based upon the DTO sent by presenter
  2. Passive screen -  subject of today's post. I've already blogged about it, but that post was more of L100 type of post where I deliberately melt down presenter and model, so the most important thing presenter - view wire up could be more clear. Today's post would dig deeper into the Passive view and give a couple of additional information's with proper implementation model presenter separation of concerns .

Stage set up

Today's example would deliberately use the same use case, model and view markup as the one  used in previous supervising controller blog post to makes easier understanding distinction between the two flavors of the MVP pattern implementation, so check out that post for the details on how the example stage is set up before continuing.

Here, I'll just summary example set up into:

  1. Use case describes a web page which should provide database user search functionality for a given user name. Web page is presenting first name, last name and collection of user addresses. In case no user is found for a given username, error message should be presented. If user updates first name or last name, those changes has to be persisted.
  2. Model is represented with a type implementing the IUserService interface which defines two methods: string GetUser(string userName) and void SaveUser(User user). Retrieving/Saving of user should be done by using

The page in design mode would look something like this:

image  

Passive (screen) view

Passive view is a type of MVP design pattern based on a concept that the view of interface should be abstracted representation of the view, where view UI elements would be represented with .NET plain data types. In that way, presenter is (on decoupled way) aware of view UI elements so he could set/get values directly from the view, without the need of any DTO being used by view as a way of viewer<->presenter communication. .

The biggest advantage of the passive view is that leaves the view with only a "few lines of code" making it so simple that it doesn't require testing different then taking a quick look on the view code. Presenter is now having absolute control on view behavior so testing the presenter is all we have to do to verify even that the view works ok. Another very important advantage is the one David Hayden likes so much, and that is: it enables active work separation between the UX's and DEV's, because DEV can make the complete view page logic without having a page(view) at all. All he has to do is to code against agreed view interface (page approximation). All UX guy has to do is to implement view interface on the view (which is pretty trivial thing to be done) and after that he has all the freedom to work not caring what DEV does.

From what I learned from UX people I met so far, they want to stay away of code behind as much as possible. With supervising controller MVP implementation the question of ownership of actual page/control code behind (aspx.cs/ascx.cs) is unclear and usually ends having both UX/DEV working on it. With passive view, UX owns aspx and aspx.cs and DEV owns presenter and model. Amen! :)

The disadvantage of empowering the presenter is that it can lead to very complex presenter classes with a lot of things to be tested where some of them ay not be related strictly to business logic.

Anyhow, IMHO passive view extreme testability provides much greater gain than it causes pain with having more complex presenters. When we add to that equation ability of effective team multitasking  enabling

View interface

View interface in passive view flavor of MVP is usually having much more members defined (comparing with supervising controller) because each one of the interface members  represents abstraction of a view UI element. That allow

That's how:

  • labels are represented with string setter-only properties,
  • text boxes with getter/setter strings, 
  • repeaters, data lists, data grids  etc are represented with collections of strings or other plain types
  • check boxes are represented with boolean values
  • option buttons with integer/string (containing the pointer to the selected one)
  • drop down lists/lists  - Dictionary<TKey, TValue>

image

Interface contains next abstract representations:

  • Collection of strings representing a collection of user addresses. (setter only -> read only)
  • First name and last name strings - (getter and setter) 
    There is requirement that user should be able to update those two so the presenter cares about the changes 
  • Search criteria - (getter only)
    Presenter is not setting the value of the search criteria text box. It just reads the value user typed in.
  • ShowResultPannel setter only boolean property  which enables presenter to perform explicit action on view (hide/show the result panel)
  • Label showing error messages is abstracted to StatusMessage  (setter only -> read only)  

There are no events in the view interface

There is no event definition as an abstraction of the search button click event, because view would in button click event handler just directly call the public presenter method which should occur when a button click happens.

The downside of that approach is that now view is coupled to ("view knows") the presenter which in theory is not a best practice, but in real world I could see it only causing problems if you plan to use different presenters with the same view. In my personal experience and in general reading the MVP related stuff on the web, I couldn't find anyone speaking about that use case as something from the real world, so I am not therefore concerning this as a viable architecture concern.

What we gain from this approach is that we have now much simpler view-presenter wire up code where there is no need for event publishing in view nor for event subscribing code in presenter. Testability is increased to because it is much simpler to just call public presenter method to trigger that behavior then to trigger it indirectly through simulation of the event occurrence. Take a look   how complex test code for that event evocation could look :)

There are no DTO (data transfer object)

Due to the fact that presenter is indirectly manipulating the view elements and that the view is "dumb", there is no need for using any DTO objects because there is no need for presenter to pass any kind of data to the view, because view doesn't do anything.

View implementation

Presenter wire up

The pattern used for establishing wire up between the view and presenter is dependency injection - constructor type.

The  key concepts of this wire up are:

  • presenter has a constructor accepting parameter of view interface type
  • view implements the view interface
  • view in page load constructs a presenter instance and throw himself to the presenter
  • presenter has a pointer then to the view, but the pointer type is view interface so presenter is not knowing nothing about the UI specific aspects of the view he's been injected
  • view keeps created instance of the pointer in the page field to support presenter method invocation in case of specific UI events

In code that would look like this:

   1: private UserDetailsPresenter _presenter;
   2:  
   3: protected void Page_Load(object sender, EventArgs e)
   4: {
   5:     // injecting the view into the presenter
   6:     _presenter = new UserDetailsPresenter(this);
   7: }

Implementing view interface

The view is a web page implementing the view interface just to perform simple mapping of the view interface members and appropriate view UI elements they represent, something like this:

   1: #region IUserDetailsView Members
   2:  
   3: public IEnumerable<string> Address
   4: {
   5:     set
   6:     {
   7:         AdressesRepeater.DataSource = value;
   8:         AdressesRepeater.DataBind();
   9:     }
  10: }
  11:  
  12: public string FirstName
  13: {
  14:     get { return FirstNameTextBox.Text; }
  15:     set { FirstNameTextBox.Text = value; }
  16: }
  17:  
  18: public string LastName
  19: {
  20:     get { return LastNameTextBox.Text; }
  21:     set { LastNameTextBox.Text = value; }
  22: }
  23:  
  24: public string SearchCriteria
  25: {
  26:     get { return SearchCriteriaTextBox.Text; }
  27: }
  28:  
  29: public bool ShowResultPannel
  30: {
  31:     set { resultPanel.Visible = value; }
  32: }
  33:  
  34: public string StatusMessage
  35: {
  36:     set { ResultLabel.Text = value; }
  37: }
  38: #endregion

In line 7, we see that when presenter .sets the Address property of the view interface that would result with that string collection being used as a data source of a repeater. That's how presenter knows only about the collection string property but indirectly setting it, he works with the UI elements itself without having any knowledge about them.

Properties FirstName and LastName perform wire up of the view interface members to the text property of appropriate textboxes. When presenter set a value for a IUserDetailsView.FirstName he fills the FirstNameTextBox.Text value. When presenter reads the IUserDetailsView.LastName, he reads the LastNameTextBox.Text control property value. All the time, not knowing a thing about text boxes, presenter work with them.

In line 26, Presenter would read the string value of IUserDetailsView.SearchCriteria which would return him the entry user made in SearchCriteriaTextBox control

In line 31, presenter setting the boolean value of IUserDetailsView.ShowResultPannel, hide/show the panel containing the results.

At the end, in line 36, presenter can set the string value of IUserDetailsView.StatusMessage property if he wants to present a error message on the web page

UI specific event handling

As I've previously said, in this example there won't be any event driven code to support view event handling. Instead, view would make a direct call  to presenter, something like this:

   1: protected void OnUserSearchButtonClicked(object sender, EventArgs e)
   2: {
   3:     _presenter.OnUserSearched();
   4: }
   5:  
   6: protected void OnUserSaveButtonClicked(object sender, EventArgs e)
   7: {
   8:     _presenter.OnUserSave();
   9: }

As we can see in line 3 and 5, the view simply calls a presenter method of on a field containing presenter instance and by that simple command, when save button would be clicked, presenter's OnUserSave() method would be called.

Presenter

Class diagram of the presenter looks like this:

image

As we can see from the diagram, presenter has:

  • two public constructors: one accepting the view and service interface and one accepting only the view with poor man dependency injection creation of the default service interface implementation
  • Two public methods containing the logic needed for handling appropriate UI view events.
  • Presenter has also three fields which are holding the presenter pointers to the view and service layers and to  user originally retrieved from service layer

Presenter initialization

All the business logic of controlling,presenting and updating model and interaction with view should be encapsulated in the presenter. In this example Presenter is getting pointer to the view interface and model services through the utilization of constructor type of dependency injection design pattern.

   1: private readonly IUserService _userService;
   2: private readonly IUserDetailsView _view;
   3:  
   4: public UserDetailsPresenter(IUserDetailsView view)
   5:     : this(view, new UserServiceStub())
   6: {
   7: }
   8:  
   9: public UserDetailsPresenter(IUserDetailsView view, IUserService userService)
  10: {
  11:     _view = view;
  12:     _userService = userService;
  13: }

In lines 11 and 12, presenter is storing interface representations of the given view and service. That's how the _view field would be a pointer to a view web page and presenter would working with that field work with a web page not knowing that (I've explained how in view interface implementation section)

Presenter implementing view required logic

We have two methods in presenter which perform certain actions for a view, when view requests them.

We saw that view would in case of button clicked directly call presenter public method which would perform appropriate action

OnUserSave method implementation

   1: public void OnUserSave()
   2: {
   3:     bool isFirstNameChanged = (_originalUser.Name != _view.FirstName);
   4:     bool isLastNameChanged = (_originalUser.Surname != _view.LastName);
   5:     if (isFirstNameChanged || isLastNameChanged)
   6:     {
   7:         _originalUser.Name = _view.FirstName;
   8:         _originalUser.Surname = _view.LastName;
   9:         _userService.SaveUser(_originalUser);
  10:     }
  11: }

In line 3 and 4, we see how presenter checks if the user edited presented user data. Presenter does that by comparing the values of user instance retrieved from a database with appropriate  view interface implementation.

Example: when in line 3 presenter compares originally retrieved user name with _view.FirstName he compares behind the curtain original value with the text of the FirstNameTextBox control because view is implementing the view interface by wiring up view interface members with real controls.

That's how presenter talks with a view through the view interface

OnUserSearch method implementation

   1: public void OnUserSearched()
   2:     {
   3:         _view.ShowResultPanel = false;
   4:         if (string.IsNullOrEmpty(_view.SearchCriteria))
   5:         {
   6:             _view.StatusMessage = "User name can not be null";
   7:             return;
   8:         }
   9:  
  10:         _originalUser = _userService.GetUser(_view.SearchCriteria);
  11:         if (_originalUser == null)
  12:         {
  13:             _view.StatusMessage = String.Format("There's no user found for user name:{0}", _view.SearchCriteria);
  14:             return;
  15:         }
  16:  
  17:         _view.FirstName = _originalUser.Name;
  18:         _view.LastName = _originalUser.Surname;
  19:         _view.Address = MapUserAddress(_originalUser.Addresses);
  20:  
  21:         _view.ShowResultPanel = true;
  22:     }

In line 3, presenter sets a _view.ShowResultPanel=false, which would  result executing of the next page (view) code

public bool ShowResultPannel
{
    set { resultPanel.Visible = value; }
}

which would hide the old search results on the start of the search.

In line 4, presenter checks if user entered any search criteria by accessing text property of the SearchCriteriaTextBox control via view interface SearchCriteria property. In case user didn't enter any search criteria, presenter in line 6 sets a view interface member value which would result with view showing that string on ResultLabel.

In line 10, presenter reaches out to model and retrieves a user for a given search criteria.

In line 11, presenter checks the result of user retrieval and if there are no users retrieved for a given criteria, shows an error message in line 13. 

In lines 17,18,19 presenter sets the view interface members values which result with appropriate UI actions - setting text boxes and repeater data bounding.

At the end, in line 21, presenter shows result panel to the user

Summary

Passive view pattern is a UI design pattern which moves most of the responsibilities from web pages leaving them to do just plain view interface implementation with wiring up interface members with appropriate UI control properties.

That gives all the brains to presenter, which enables TDD approach because presenter is UI technology  agnostic, so we are able to write test methods against it and by testing the presenter we are testing indirectly the page itself.

That and its suitability for decoupled team work makes passive view my favorite  flavor of the MVP/MVC pattern regardless of the downsides  it has

Source code of today's example can be found here:   Passive (screen) view source code

What is next?

Next parts of the MVP saga would try to take a close look on a conceptual differences between the MVC and MVP patterns which would be helpful to know considering all the buzz future Microsoft MVC ASP NET framework gets these days.

I plan to write also about MVP pattern usage in cases of composite pages (pages controlling controls) which would be something very interested because I am fan of controls having their own presenters so it could be quite challengeable on a first encounter handling all this interfaces on a elegant way.

At the end of the MVP post series I would make a detail post about how to test in real world MVP patterns which would be a more advanced and detailed version of the L100 post I wrote already

转载于:https://www.cnblogs.com/jeriffe/articles/2392492.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值