Well, MVP / Observer pattern rocks, you all know I feel that way. Well, there are those who don't. I heard rumblings that basically it's an abuse of the observer pattern to make an academic point. I think that's a silly argument because using the observer pattern makes your view contract stronger.
With the observer pattern, you can not only define what the view HAS, but also what it CAN DO. It also adheres to Tell, Don't Ask.
Well, there's more too. I'm a firm believer in DRY. MVP pages all have a presenter on their page, and I always new them up in onload. I figured I could move that into a base class.
So I did.
5 public abstract class MvpPage:Page
6 {
7 ///
8 /// The Presenter for this page
9 ///
10 private object Presenter
11 {
12 get; set;
13 }
14 protected override void OnLoad(System.EventArgs e)
15 {
16 base.OnLoad(e);
17 Presenter = PresenterFactory.GetPresenterFor(this);
18 }
19 }
I didn't want to use Type parameters since that would defeat the whole purpose of making the page unaware of it's presenter.
The work is all really done in PresenterFactory, so let's get to that.
Here is the unit test for GetPresenterFor.
1 using System;
2 using MyDumbView.Presentation;
3 using MyDumbView.UnitTests.Presentation.TestAssembly.Presentation.Presenters;
4 using MyDumbView.UnitTests.Presentation.TestAssembly.Presentation.Views;
5 using NUnit.Framework;
6 using NUnit.Framework.SyntaxHelpers;
7
8 namespace MyDumbView.UnitTests.Presentation
9 {
10 [TestFixture]
11 public class PresenterFactoryTestFixture:AssertionHelper
12 {
13 [Test]
14 public void GetPresenterFor_returns_the_right_type_of_presenter()
15 {
16 //Arrange
17 var view = new TestView();
18 //Act
19 var presenter = PresenterFactory.GetPresenterFor(view);
20 //Assert
21 var expectedType = typeof (TestPresenter);
22 Expect(presenter.GetType(),Is.EqualTo(expectedType));
23 }
24
25 }
26
27 namespace TestAssembly.Presentation.Views
28 {
29 ///
30 /// My Test Interface
31 ///
32 public interface ITestView{}
33 ///
34 /// Concrete class of
35 ///
36 public class TestView:ITestView{}
37 }
38 namespace TestAssembly.Presentation.Presenters
39 {
40 public class TestPresenter
41 {
42 public TestPresenter(ITestView view)
43 {
44 }
45 }
46 }
47 }
Makes sense right?
Well, here's the factory.
1 using System;
2
3 namespace MyDumbView.Presentation
4 {
5 ///
6 /// A factory that that returns presenters for views
7 ///
8 public class PresenterFactory
9 {
10 ///
11 /// Returns a presenter for the view, very opinated code based on naming conventions.
12 ///
13 ///
14 ///
15 ///
16 public static object GetPresenterFor(object view)
17 {
18 var type = view.GetType();
19 var viewTypeName = GetViewTypeName(type);
20 var presenterType = GetPresenterType(viewTypeName);
21 return GetPresenter(presenterType, view);
22 }
23 private static string GetViewTypeName(Type type)
24 {
25 return PresenterFactoryNameParsingStrategy.GetTypeNameForView(type);
26 }
27 private static Type GetPresenterType(string viewTypeName)
28 {
29 var presenterName = GetPresenterTypeName(viewTypeName);
30 var presenterType = Type.GetType(presenterName);
31 if (presenterType == null)
32 {
33 throw new Exception(String.Format("Can not find class {0}.", presenterName));
34 }
35 return presenterType;
36 }
37 private static string GetPresenterTypeName(string viewTypeName)
38 {
39 return PresenterFactoryNameParsingStrategy.GetTypeNameForPresenter(viewTypeName);
40 }
41 private static object GetPresenter(Type presenterType, object view)
42 {
43 var presenter = Activator.CreateInstance(presenterType, new[] { view });
44 if (presenter == null)
45 {
46 throw new Exception(String.Format("Can not instanciate class {0}. Does the class have a public constructor taking a single argument?", presenterType));
47 }
48 return presenter;
49 }
50
51
52 }
53 }
1 using System;
2
3 namespace MyDumbView.Presentation
4 {
5 ///
6 /// Encapsulates our opinionated naming stategy for Views and Presenters
7 ///
8 internal static class PresenterFactoryNameParsingStrategy
9 {
10 ///
11 /// Returns the Type name for a MVP view
12 ///
13 ///
14 ///
15 public static string GetTypeNameForView(Type type)
16 {
17 foreach (var iface in type.GetInterfaces())
18 {
19 if (iface.Name.EndsWith("View"))
20 {
21 return iface.AssemblyQualifiedName;
22 }
23 }
24 throw new Exception("View does not adhere to Naming conventions.");
25 }
26 ///
27 /// Returns the name for a MVP presenter given it's view
28 ///
29 ///
30 ///
31 public static string GetTypeNameForPresenter(string viewTypeName)
32 {
33 var name = viewTypeName.Replace(".I",".")
34 .Replace("Views", "Presenters");
35 var nameParts = name.Split(',');
36 var typeName = name.Split(',')[0];
37 var assemblyName = nameParts.Length > 1
38 ?
39 nameParts[1]
40 :
41 String.Empty;
42 typeName = typeName.Replace(".I", ".").Replace(".Views.", ".Presenters.");
43 var endIndex = typeName.Length - "View".Length;
44 typeName = typeName.Substring(0, endIndex) + "Presenter";
45 return String.Format("{0}, {1}", typeName, assemblyName);
46 }
47 }
48 }
1 using System;
2 using MyDumbView.DomainModel;
3 using MyDumbView.Presentation;
4 using MyDumbView.Presentation.Views;
5
6
7 public partial class _Default :MvpPage,IPersonEditorView
8 {
9
10 ///
11 /// Wires up events
12 ///
13 ///
14 protected override void OnInit(EventArgs e)
15 {
16 base.OnInit(e);
17 LoadButton.Click += delegate { PersonLoaded(this, e); };
18 SaveButton.Click += delegate { PersonSaved(this, e); };
19 }
20 #region IPersonEditorView Methods
21
22 ///
23 /// The
24 ///
25 public int? PersonId
26 {
27 get
28 {
29 int x;
30 if (int.TryParse(PersonIdControl.Text,out x))
31 {
32 return x;
33 }
34 return null;
35 }
36 set { PersonIdControl.Text = value.ToString(); }
37 }
38 ///
39 /// The
40 ///
41 public string FirstName
42 {
43 get { return PersonFirstNameControl.Text; }
44 set { PersonFirstNameControl.Text = value; }
45 }
46 ///
47 /// The
48 ///
49 public string LastName
50 {
51 get { return PersonLastNameControl.Text; }
52 set { PersonLastNameControl.Text = value; }
53 }
54 ///
55 /// Event raised when Saved is Clicked
56 ///
57 public event EventHandler PersonSaved = delegate { };
58 ///
59 /// Event raised when Load is clicked
60 ///
61 public event EventHandler PersonLoaded = delegate { };
62
63 #endregion
64 }
Great stuff Elliot, thanks for sharing your ideas.
ReplyDeleteThanks Matt...
ReplyDeleteBTW, your http://blog.mattwynne.net/2007/06/13/mvp-smells/ article is bookmarked and I send developers new to MVP there constantly!
rolex watches
ReplyDeleteHello mate nice bblog
ReplyDelete