【
注意Presenter跟View之间的关系,从Presenter出发的箭头并没有直接连在View上,而是跟View的“接口”相连,这就说明一个很重要的一点就是Presenter并不是直接跟具体的View紧密联系在一起,Presenter只知道View的Interface。这样做的好处就是让Presenter可以独立于具体的View而实现所有的logic, 如果之后改变View,只要相应的Interface没有更改,Presenter不用做任何改动。
】
As we can see from this diagram:
(1) View contains the Presenter instance (view "knows" presenter)
- (2) Presenter is the only class knowing how to reach to model and retrieve the data needed for performing
- business logic
- (3) Presenter talks to the View through the view interface (abstracted representation of the View without UI
- specific attributes)
- (4) View doesn't know nothing about the Model
【
2006年7月份,两岁大的MVP就“退休”了,取而代之的是基于MVP的两个“变种”,一个叫Supervising Controller, 另外一个叫Passive View。
】
In July 2006, two year old Model View Presenter pattern has been retired and two variations from original MVP pattern came up:
- Supervising controller - where the view is handling UI operations on his own based upon the DTO sent by presenter
- Passive screen (View)
Passive 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: 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.
【
Passive View的最大好处在于可以把UI设计和UI逻辑处理完全分开来做,这样DEV完全可以不需要设计Page,只需要有一个View的Interface就可以完成所有的逻辑处理工作。
负责UI的人只需要操作aspx和aspx.cs(这里面几乎没有什么代码,因为相关的事件处理都是放在Presenter中,UI只需要调用Presenter的方法即可), 而负责Business Logic的人员负责presenter和model。
】
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.
【Passive View里面的属性(property/field)会相对多一些, 因为它要实现定义在Interface里面的所有属性。】
There are no events in the view interface
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.
Presenter
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: }
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.
=================================================================================
Supervising Controller
Supervising controller is a type of MVP design pattern based on a concept of data context binding where the view is implementing some "binding" logic on a object received from presenter which contains certain group of business data needed for the whole view. The view is therefore performing some functionality on it's own.
The biggest advantage of supervising controller is in the fact that presenter is not aware of the view internals, which allows reuse of one presenter with multiple views using the same context data source but with different UI elements. Also presenters are in general much simpler comparing with the Passive view presenters which need to take care about every single aspect of the view.
View Interface
public interface IUserDetailsView { string StatusMessage { set; } UserDTO ContextData { set; get; }
event EventHandler<SearchEventArgs> Search; event EventHandler<EventArgs> Save; }
【 注意里面有两个Event Search 和 Save, 还有一个DTO -- ContextData, 这些在Passive View里面都是没有的。】
Interface contains next abstract representations:
- (1) label showing error messages is abstracted to StatusMessage - string setter property.
Labels and in general other "read only" controls should be represented without getter, because user can not - modify their value and therefore presenter is not interested in reading their value。
- (2)Find user button is abstracted to Search event represented with generic EventHandler<SearchEventArgs>
- delegate signature.
(3)Save button is abstracted to a Save event argument with a default event argument signature - (4)Last part of the view interface is a ContextData property. The purpose of this property is to behave as a
- "view data source".
The communication between the presenter and the view is been done through that context data object
Presenter is responsible to set the context data object value with values retrieved from the model and the view is supposed to update itself from that context data object value. View is also responsible to update appropriate - context data properties with the values of the UI specific elements. Presenter then detects all the changes and updates the model with them.
View DTO (data transfer object)
Purpose of the DTO (Data transfer object) is to be information mule, without any logic inside which has a collection of properties in which data is carried in encapsulated manner. DTO pattern is essential one in implementing SOA systems, but I'll skip that aspect for now.
The biggest question related to View DTO is when we should use it at all? The downside of DTO is that we have to map domain object to the DTO which introduces one more level of indirection ("There is no problem which can not be solved with one more level abstraction") and additional coding.
In our little example the IUserService.Get method returns the User object so why not pass that object to the view and skip the mapping hustle?
There are couple of cases which could be signals that the DTO mapping is required:
- (1)User can be very heavy object which wouldn't be serialization optimal if we have distributed system scenarios
- (2)In case of Active Record pattern based design it could expose some methods to the UI developer (user.Save()) which could confuse him or tempt him to make view code logic heavy avoiding presenter
- (3) We need to present only a subset of the information domain object carries (in this example we don't need credit card information on UI level)
- (4) We need to translate some of the strongly typed data to more "UI friendly" type of data. (In the example UserAddress type collection data would be translated to a collection of the strings so the repeater could perform easier data binding)
Data mapping
Data mapper design pattern default interpretation includes into the set of data mapper responsibilities beside mapping the data also the data retrieval activities. Although, I agree that is the official and correct implementation, I don't like that small functional add on because it breaks the separation of concern principles. IMHO, purpose of the data mapper is just transformation of the already loaded data. Mapper should only know "how to map".
private static IList<UserAddress> MapUserAddress(IEnumerable<string> adresses) { IList<UserAddress> result = new Collection<UserAddress>(); IEnumerable<UserAddress> stronglyTypedUserAddresses = ParseAddressStrings(adresses); foreach (UserAddress userAddress in stronglyTypedUserAddresses) { result.Add(userAddress); } return result; } private static IEnumerable<UserAddress> ParseAddressStrings(IEnumerable<string> adresses) { foreach (string address in adresses) { string[] addressParts = address.Split(','); new UserAddress(addressParts[0], addressParts[1]); } }
The method calls the user address collection mapping method which transform the collection of the
UserAddresses into the IEnumerable<string> utilizing the yield c#
View implementation
public UserDTO ContextData { get { return Session["Context"] as UserDTO; } set { Session["Context"] = value; OnContextDataBound(); } }
private void OnContextDataBound() { FirstNameTextBox.Text = ContextData.FirstName; LastNameTextBox.Text = ContextData.LastName; AdressesRepeater.DataSource = ContextData.Addresses; AdressesRepeater.DataBind(); resultPanel.Visible = true; }
View interface events
protected void OnUserSearchButtonClicked(object sender, EventArgs e) { if (Search != null) Search(this, new SearchEventArgs(SearchCriteriaTextBox.Text)); } protected void OnUserSaveButtonClicked(object sender, EventArgs e) { if (Save != null) Save(this, new EventArgs()); }
Presenter
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.
public UserDetailsPresenter(IUserDetailsView view, IUserService userService) { _view = view; _userService = userService; }
public UserDetailsPresenter(IUserDetailsView view) : this(view, new UserServiceStub()) { }
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 to request something from a presenter the view would raise an event and due to the fact that presenter is in its constructor subscribing to the those events, when the view would send event presenter private method would be executed
NOTE: This is standard 'Fowler like' event based implementation of the view presenter communication. The other (simpler) way how this could be implemented is that presenter could define as public methods implementing the logic required by a view and the view could directly call them with something like: _presenter.OnUserSave() without having the need for defining events and related code concepts. The price of simplicity is that the presenter broke his encapsulation from the view and that the view implementation is now tied to the specific presenter implementation. For a lot of people, this is fair trade off, so in my personal development I am implementing it like that.
The only reason why I am presenting it using events is to avoid comments that is "not by the book" and "too coupled" :)
private void OnUserSave(object sender, EventArgs e)
{ if (_view.ContextData.IsDirty) { User user = new User(); User domainUser = DataMapper.Translate(_view.ContextData); _userService.SaveUser(domainUser); } }
Summary
Supervising Controller pattern is a UI design pattern which takes most complex responsibilities from the back of the web pages but still leaving them fair amount of autonomy. That makes it more suitable for implementing more complex UI pages/controls because having oversized presenter and undersized view can sometimes cause in teams overwhelming of developers working on presenter and leaves underused the one working on the view