我正在学习在C#中将MVP应用到一个简单的WinForms应用程序(只有一个表单),并在static void Main()
中创建主要演示者时遇到问题。从Presenter公开View以将其作为参数提供给Application.Run()是一个好主意吗?
目前,我已经实现了一种方法,允许我不将View作为Presenter的属性公开:
static void Main()
{
IView view = new View();
Model model = new Model();
Presenter presenter = new Presenter(view, model);
presenter.Start();
Application.Run();
}
Presenter中的Start和Stop方法:
public void Start()
{
view.Start();
}
public void Stop()
{
view.Stop();
}
View中的Start和Stop方法(Windows窗体):
public void Start()
{
this.Show();
}
public void Stop()
{
// only way to close a message loop called
// via Application.Run(); without a Form parameter
Application.Exit();
}
Application.Exit()调用似乎是一种关闭Form(和应用程序)的不雅方式。另一种方法是将View作为Presenter的公共属性公开,以便使用Form参数调用Application.Run()。
static void Main()
{
IView view = new View();
Model model = new Model();
Presenter presenter = new Presenter(view, model);
Application.Run(presenter.View);
}
Presenter中的Start和Stop方法保持不变。添加了一个附加属性以将视图作为表单返回:
public void Start()
{
view.Start();
}
public void Stop()
{
view.Stop();
}
// New property to return view as a Form for Application.Run(Form form);
public System.Windows.Form View
{
get { return view as Form(); }
}
View中的Start和Stop方法(Windows窗体)将按如下方式编写:
public void Start()
{
this.Show();
}
public void Stop()
{
this.Close();
}
有人能建议哪种方法更好,为什么?或者有更好的方法来解决这个问题?
答案 0 :(得分:9)
以下内容如何:
// view
public void StartApplication() // implements IView.StartApplication
{
Application.Run((Form)this);
}
// presenter
public void StartApplication()
{
view.StartApplication();
}
// main
static void Main()
{
IView view = new View();
Model model = new Model();
Presenter presenter = new Presenter(view, model);
presenter.StartApplication();
}
这样,您不需要将视图暴露给外部。此外,视图和演示者都知道这个视图已经作为“主要形式”开始,这可能是一个有用的信息。
答案 1 :(得分:5)
我会选择第二种方法。 您还可以通过简单地将视图转换为void Main中的表单来消除额外的属性,因为您知道它在那时仍然是一个表单(我认为没有理由使它更通用,因为它只是启动winform app )
Application.Run(view as Form);
答案 2 :(得分:1)
如果您允许多种方式退出应用程序(例如:退出的菜单项),或者如果您在某些条件下阻止关闭应用程序,则事情会变得复杂一些。在任何一种情况下,应用程序关闭的实际调用通常应该从演示者代码调用,而不是简单地关闭具体视图。这可以通过使用Application.Run()或Application.Run(ApplicationContext)重载并通过控制反转来公开应用程序退出操作来完成。
注册和使用应用程序退出操作的确切方法取决于您使用的IoC机制(例如:服务定位器和/或依赖注入)。由于您尚未提及当前的IoC方法,因此这里的示例独立于任何特定的IoC框架:
internal static class Program
{
[STAThread]
private static void Main()
{
ApplicationActions.ExitApplication = Application.Exit;
MainPresenter mainPresenter = new MainPresenter(new MainView(), new Model());
mainPresenter.Start();
Application.Run();
}
}
public static class ApplicationActions
{
public static Action ExitApplication { get; internal set; }
}
public class MainPresenter : Presenter
{
//...
public override void Stop()
{
base.Stop();
ApplicationActions.ExitApplication();
}
}
这种基本方法可以很容易地适应您首选的IoC方法。例如,如果您正在使用服务定位器,则可能需要考虑至少删除ApplicationActions.ExitApplication属性上的setter,并将该委托存储在服务定位器中。如果要保留ExitApplication getter,它将为服务定位器实例检索器提供简单的外观。 e.g:
public static Action ExitApplication
{
get
{
return ServiceLocator.GetInstance<Action>("ExitApplication");
}
}
答案 3 :(得分:0)
您可以用一百种方法来实现关注点可分离性的最终目标。这里没有硬性规定,基本思想是演示者处理视图的表示逻辑,而视图仅具有其自己的GUI特定类和内容的愚蠢知识。我可以想到的一些方法(概括地说):
1)视图启动了一切,让它决定了其演示者。您开始像new View().Start();
// your reusable MVP framework project
public interface IPresenter<V>
{
V View { get; set; }
}
public interface IView<P>
{
P Presenter { get; }
}
public static class PresenterFactory
{
public static P Presenter<P>(this IView<P> view) where P : new()
{
var p = new P();
(p as dynamic).View = view;
return p;
}
}
// your presentation project
public interface IEmployeeView : IView<EmployeePresenter>
{
void OnSave(); // some view method
}
public class EmployeePresenter : IPresenter<IEmployeeView>
{
public IEmployeeView View { get; set; } // enforced
public void Save()
{
var employee = new EmployeeModel
{
Name = View.Bla // some UI element property on IEmployeeView interface
};
employee.Save();
}
}
// your view project
class EmployeeView : IEmployeeView
{
public EmployeePresenter Presenter { get; } // enforced
public EmployeeView()
{
Presenter = this.Presenter(); // type inference magic
}
public void OnSave()
{
Presenter.Save();
}
}
上述方法的一种变体是对视图和演示者实施更强的通用约束,但是我认为复杂性并没有带来更多好处。像这样:
// your reusable MVP framework project
public interface IPresenter<P, V> where P : IPresenter<P, V> where V : IView<P, V>
{
V View { get; set; }
}
public interface IView<P, V> where P : IPresenter<P, V> where V : IView<P, V>
{
P Presenter { get; }
}
public static class PresenterFactory
{
public static P Presenter<P, V>(this IView<P, V> view)
where P : IPresenter<P, V>, new() where V : IView<P, V>
{
return new P { View = (V)view };
}
}
// your presentation project
public interface IEmployeeView : IView<EmployeePresenter, IEmployeeView>
{
//...
}
public class EmployeePresenter : IPresenter<EmployeePresenter, IEmployeeView>
{
//...
}
缺点
涉及的步骤:
IEmployeeView
PresenterFactory
并从视图构造函数传递this
来实例化演示者new EmployeeView()...
一样开始。 2)演示者启动事情,让它决定其观点。您开始像new Presenter().Start();
在这种方法中,演示者通过某种依赖注入来实例化其自己的视图(如方法1),或者可以将视图传递给演示者的构造函数。例如
// your reusable MVP framework project
public abstract class IPresenter<V> // OK may be a better name here
{
protected V View { get; }
protected IPresenter()
{
View = ...; // dependenchy injection or some basic reflection, or pass in view to ctor
(View as dynamic).Presenter = this;
}
}
public interface IView<P>
{
P Presenter { get; set; }
}
// your presentation project
public interface IEmployeeView : IView<EmployeePresenter>
{
void OnSave(); // some view method
}
public class EmployeePresenter : IPresenter<IEmployeeView>
{
public void Save()
{
var employee = new EmployeeModel
{
Name = View.Bla // some UI element property on IEmployeedView interface
};
employee.Save();
}
}
// your view project
class EmployeeView : IEmployeeView
{
public EmployeePresenter Presenter { get; set; } // enforced
public void OnSave()
{
Presenter.Save();
}
}
涉及的步骤:
IEmployeeView
new EmployeePresenter(...
一样开始。3)基于事件的观察者风格
在这里,您可以像方法1一样封装视图中的演示者(实例化视图中的演示者),也可以像方法2一样封装视图中的演示者(实例化视图中的实例化视图),但是根据我的经验,后者将始终是更简洁的设计。例如后者:
// your reusable MVP framework project
public abstract class IPresenter<V> where V : IView
{
protected V View { get; }
protected IPresenter()
{
View = ...; // dependenchy injection or some basic reflection, or pass in view to ctor
WireEvents();
}
protected abstract void WireEvents();
}
// your presentation project
public interface IEmployeeView : IView
{
// events helps in observing
event Action OnSave; // for e.g.
}
public class EmployeePresenter : IPresenter<IEmployeeView>
{
protected override void WireEvents()
{
View.OnSave += OnSave;
}
void OnSave()
{
var employee = new EmployeeModel
{
Name = View.Bla // some UI element property on IEmployeedView interface
};
employee.Save();
}
}
// your view project
class EmployeeView : IEmployeeView
{
public event Action OnSave;
void OnClicked(object sender, EventArgs e) // some event handler
{
OnSave();
}
}
// you kick off like new EmployeePresenter()....
缺点:
涉及的步骤:
IEmployeeView
new EmployeePresenter()...
一样开始。语言的局限有时会使设计模式更加困难。例如,如果在C#中可能实现多重继承,那么只有一个抽象的基本视图类具有所有实现细节,只是可以由视图类实现的UI特定组件除外。没有演示者,经典的多态性和简单的死法!不幸的是,这是不可能的,因为.NET中的大多数视图类(例如WinForms的Form
)已经继承自超级视图类。因此,我们必须实现一个接口并进行合成。另外,C#不允许接口实现中包含非公共成员,因此我们被迫公开在IEmployeeView
中指定的所有成员,这会破坏视图类的自然封装规则(即视图项目中的其他视图)可以查看与它们无关的EmployeeView
的详细信息)。无论如何,使用C#扩展方法的强大功能,可以采用更为简单但非常有限的方法。
4)扩展方法方法
这真是愚蠢。
// your presentation project
public interface IEmployeeView
{
void OnSave(); // some view method
}
public static class EmployeePresenter // OK may need a better name
{
public void Save(this IEmployeeView view)
{
var employee = new EmployeeModel
{
Name = view.Bla // some UI element property on IEmployeedView interface
};
employee.Save();
}
}
// your view project
class EmployeeView : IEmployeeView
{
public void OnSave()
{
this.Save(); // that's it. power of extensions.
}
}
缺点:
涉及的步骤:
IEmployeeView
this....
扩展方法在所有2和3中,对我来说看起来更好。