I've spent a lot of time, over the last year or so, working in Model-View-Presenter code. During this time, I've had a lot of questions; come up with a lot of different answers to the same questions; implemented a lot of different infrastructures for MVP including auto-magic view instantiation, manual view and presenter instantiation; and changed my mind about how best to implement certain portions of MVP a thousand times. Through all of this, I've come to some fairly good conclusions and a set of best practices that are helping my team(s) build better software, faster.
My current project list includes several developers that have not worked with me, or with MVP. As such, I'm seeing these developers go through the very same questions, pain, trials and tribulations that I've gone through in the last year. Hopefully, I'm able to prevent much of this pain for them - when I see a person questioning or implementing in ways that I don't think are correct, I do what I can to provide additional guidance, information on my experience with what they are doing and general direction for moving forward.
With this in mind, I can assume that there are a lot of developers out there asking the same questions and traveling down the same paths. My hope is that I can provide some answers and clarifications to help make the journey easier, not only for those that I work with, but also for those learning this on their own.
Assumptions
I'm assuming that you are familiar with several key concepts that MVP deals with, including:
I'm assuming that you have some knowledge of and experience writing MVP code, and that you are searching for answers to questions in how you should implement what, where.
I'm assuming that you are doing an MVP setup because you want testability in your code, aside from the other benefits that this separation of concerns gives you. Many of my comments and best practices assume that you are unit testing.
And lastly, I'm implementing MVP with these best practices in C# and Visual Studio 2005 - my current language and environment of choice. These concepts, though, are (mostly) language and environment agnostic. You should be able to apply them to any .NET language, Java, maybe even Ruby / RoR (though it's likely that these concepts are built into RoR); and these concepts are not limited to web development. I am currently coding client-server windows applications, pocket pc applications and web applications, using MVP very successfully.
1) The Presenter is the Active Controller; The View is a Passive Doer
Rule #1 - The view is a passive, ignorant doer. It has no knowledge of when to do anything. It has no control over anything. It does what it's told, when it's told to do so, by the presenter. The only thing the view knows, is *how* to do what the presenter tells it to do. This probably the most important concept to get right, when implementing an MVP setup - all other concepts, implementations and techniques that can be used, are mere side-effects and implementation details of this concept.
And yes, you can guess the MVP camp that I live in, based on this statement. :)
(Note: Don't confuse the term "active controller" with MVC, or the "supervising controller" MVP pattern. I'm using this term very loosely here, to help you see that the presenter knows what to do and when to do it; the view only knows how to do what it's told to do.)
2) Presenter: Speak to the IView Interface
Never allow your presenter to speak directly to an implementation of a View, or to know anything about any specific implementation of a View. Breaking this rule will cause your code to be un-testable by introducing references and resource requirements that are outside the scope of Unit Testing.
In fact, I will go so far as to say that the actual view implementation is completely, totally useless and irrelevant to the process that you are coding. If you find yourself in a situation where you can't code the process without having an implementation of the view, then you need to step back and re-define the boundaries between your view and presenter - see rule #1, and refactor.
But, if the view implementation is completely useless, how do know that the process being coded will work? How do we ensure that the presenter talks to the view at the right times? The answer is: mock / stub objects in your unit tests. RhinoMocks is your friend. If you don't like RhinoMocks or any other mocking container, write manual stub objects. Just remember rule #1 when you code the mocks / stubs. If you break rule number one when you implement your mock / stub, then you need to re-think / refactor.
Additionally, don't let your view's interface define anything more than how the presenter speaks to the view. This brings us to the next point:
3) View: Don't Speak Through the IView Interface*, Speak to the Presenter
If you find yourself adding methods and properties to the view interface, and these methods and properties are never called by the presenter, then you are likely trying to define methods that the view uses to speak to the presenter. Typically, in the situation, you find yourself creating method signatures on the view interface, and then in the view implementation, the implementation for that method is empty - you're not actually using it for anything because the presenter is never calling it; or, you find that the implementation of the method does nothing more than make a call to the presenter. In either of these situations, you are likely trying to define how the view speaks to the interface, in the wrong location.
What you should be doing, is creating public methods and/or properties on the Presenter so that the view implementation can call these methods and let the presenter know that something just happened.
* The major exception to this rule is when you have made the design decision for the view to communicate with the presenter via events raised by the view. In this case, your view interface defines the event, the view implementation raises the event, and the presenter listens to the event through the view interface definition.
4) View to Presenter: What Should be Communicated, and How?
Riding on the tails of item #3 is an inferred rule: what the view communicates to the presenter, and how.
The "what" is significantly more important than the "how". There are dozens, if not hundreds or thousands of ways to implement the "how" - everything from events defined in the view interface, to public methods on the presenter, to abstractions and auto-magic wiring of messages and method calls. The "how" largely depends on the MVP (or MVC) framework that you are using. I am going to assume that you are not using an existing MVP framework and that you are coding up the view and presenter by hand, allowing them to shake hands and talk, as needed.
So what should the view communicate to the presenter? Event information. Let's take a screen with a button on it, for example. When a user clicks that button, the button raises an event that is handled by the view's implementation. Once the view's implementation is handling this event, what do we do? If you look back at rule #1, the answer should be obvious - the view shouldn't do anything other than communicate this event to the presenter, so that the presenter can decide what to do. If the presenter needs the view to do something at this point, the presenter will tell the view what to do, and when.
Let's extend this to a slightly more complicated situation - let's say there is a text box on the screen, and a user of the system is allowed to change the text in that text box. What should the view do when a user changes the text in this box? The answer hasn't changed - the view should communicate this event to the presenter. The only difference is that the view is now communicating the event occurrence with the data associated with the event - i.e. the view tells the presenter that the text was changed, and here is the new text.
5) Presenter: Don't Trust the View - It's Implementation is Unknown
What is the view, in our Model-View-Presenter code? It's an interface - a definition of the methods, properties, etc. that need to be implemented so that the presenter can properly communicate with the view implementation. Step back for a minute, and think about how you code the presenter - you code against an interface definition, not against an actual implementation. If you are coding a presenter, and your presenter only knows about the view interface - then how do you know what the actual view implementation is going to do? You don't know, plain and simple.
Unfortunately, in most software development projects, a developer is given an entire M-V-P stack to code. The developer creating the presenter is likely to be the developer that is implementing the actual UI for the real view. This situation lends itself to bad coding practices - it's easy for the developer to make assumptions about how the view will behave, when coding the presenter; and letting the view make assumptions about how the presenter behaves, when coding the view implementation. Be honest with yourself - when was the last time you made an assumption about one class, while coding against it in another class? I'm guessing it was 30 minutes ago, or less. If not, then I commend you on your discipline - seriously, it's hard not to do that.
But, why are these assumptions dangerous? Why shouldn't we "know" that the view will handle data this way, at this time, when we code the presenter; or "know" that the presenter will call these view methods at this time? Let's look at this from the "allow assumptions" methodology, first.
Code a presenter that works perfectly and processes all of the data in the correct sequence; but put all of the data storage in the view's interface. Don't let the presenter hold onto any data - only let the presenter read data from the model and read data from the view, when the presenter needs data. If the presenter needs to hold onto data, push it out to the view and read it back in from the view when it's needed. What's the end-result of this situation? Assuming you are the person implementing both the presenter and the view, you are probably safe and things probably work great.
Now - what happens when Joe Frankfurter, fresh out of college, gets assigned to your project and has to change the UI elements on your view? Chances are, Joe is going to forget to hold the data properly, accidentally overwrite the values and references, and generally cause the data stored by the view to be invalid and/or corrupted. How does your presenter work, now? It's either throwing exceptions left and right; or if you've coded perfect exception handling and null reference checking, it's at least popping up warning signs all over the place, saying that it can't find the data it needs and/or asking the user to re-enter information they have already entered. The worst part; Because of the complex requirements and extraneous properties and methods on the view, Joe spent 2 weeks working on that view and you are delivering the project tomorrow... guess who gets to work all night, to fix the bugs?
Now let's look at this from the "don't assume anything" way.
Code a presenter that works perfectly and processes all of the data in the correct sequence; and code the presenter so that it maintains private references to all of the data that is needed for these processes. Ensure that the presenter is in control of the real data that is being processed, 100% of the time. When the view tells the presenter that some new data is available, the presenter validates, verifies, and quadruple-checks the new data to ensure that it really is valid and to ensure that it will not cause issues with processing. If the presenter finds that the data is not valid, it tells the view to prompt the user with a message stating why the data is not valid.
Now let's have Tom Cheeseburger join the team, fresh out of college. Put Tom on the view implementation for this new presenter and let him make all the same junior mistakes that Joe was making. What's the end result, this time? When Tom drops the object reference, corrupts the data, and overrides data with invalid values, the presenter hums along nicely and the process still succeeds. At the very worst, when Tom has the view tell the presenter some bad data, the presenter says "I don't think so" and tells the view / user why that data is bad. The end result, though, is that the presenter - having complete control over the data that it needs to process - is very stable and the process is more likely to succeed, even when the view implementation is terrible. The worst part; because of the simplified view interface with only a few properties and method, Tom spent 2 days coding this view full of bugs and you only have a week and a half until you have to deliver it. Oh, wait - that doesn't sound so bad when compared to Joe's situation. A little guidance for Tom, few informal code reviews, and Tom's view implementation is good to go with plenty of time to spare.
Which of these two situations would you rather be in? Ask yourself how many time's you been in the Joe Frankfurter situation. I'm betting it's more than you can count or remember. Now, ask yourself how many times you've been in the Tom Cheeseburger situation? Chances are, if you are not coding with good MVP standards - not very often.
Real World Example
Before you blow off my examples (re: #5) as fictional, academic, or whatever - here's a true situation that illustrates the points I'm trying to make.
I was leading a project last fall with a set delivery date. One week before that delivery date, the customer arrives and wants a demo of the product. The demo goes as expected - a few minor things here and there.. and then disaster strikes. The customer asks "so... what about requirement XYZ from the User Requirements Document?". After a few small heart attacks, I go back to my developers - who literally were fresh out of college... this is their first job, this was their first major project - and asked them to start work on the requirement with the understanding that they would need to work extra hours, etc. to get it done. Guess how long it took? ... Less than 24 hours. Two developers, fresh out of college, implemented an entire vertical slice of an application - including the enterprise integration and messaging slice - in less than 24 hours. How's that for proof in the pudding?
Summary
Take a moment and look back at the five best practices that I've listed out, and notice how the first one is very short, very terse, and very high level... then look at number five again - it's very long, very verbose, and very low level (in comparison). I've organized these best practices in this manner with a specific goal - start with a high level goal and provide supporting concepts and guidance and each level below. If I when any deeper, at this point, I would be writing code instead of providing concepts and guidance (and the code to support these concepts deserves and entire post of it's own).
Hopefully I've helped answer some of your questions about MVP. If nothing else, I hope that I've at least provided some guidance that will help you to answer the questions for yourself.
One last important note: These "best practices" are entirely subjective, based on my research, experience, and actual usage of MVP in the last year. Everything in this post is subject to change without notice, when I find a better way to do things. ... In other words, just because I said so, doesn't mean it's right. :) Please keep that in mind as you move forward with your MVP usage.