有很多方法可以实现关注点分离的最终目标,没有硬性规定,基本思想是Presenter处理视图的呈现逻辑,而View只具有其自身GUI特定类和内容的愚蠢知识。我能想到的一些方法(大致上):
1)View启动并决定其Presenter。你可以这样开始:new View().Start();
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;
}
}
public interface IEmployeeView : IView<EmployeePresenter>
{
void OnSave();
}
public class EmployeePresenter : IPresenter<IEmployeeView>
{
public IEmployeeView View { get; set; }
public void Save()
{
var employee = new EmployeeModel
{
Name = View.Bla
};
employee.Save();
}
}
class EmployeeView : IEmployeeView
{
public EmployeePresenter Presenter { get; }
public EmployeeView()
{
Presenter = this.Presenter();
}
public void OnSave()
{
Presenter.Save();
}
}
上述方法的一种变体是对视图和Presenter强制实施更强的通用约束,但我认为复杂性不如收益。类似这样的东西:
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 };
}
}
public interface IEmployeeView : IView<EmployeePresenter, IEmployeeView>
{
}
public class EmployeePresenter : IPresenter<EmployeePresenter, IEmployeeView>
{
}
缺点
涉及的步骤:
- 实现
IEmployeeView
- 通过从视图构造函数传递
this
来调用PresenterFactory
并实例化presenter
- 确保视图事件连接到其相应的presenter方法
- 开始,像
new EmployeeView()...
。
2) Presenter启动并决定其视图。您可以像这样开始:new Presenter().Start();
在这种方法中,Presenter通过某些依赖注入或其他方式实例化自己的视图(如方法1),或者视图可以传递给Presenter的构造函数。例如:
public abstract class IPresenter<V>
{
protected V View { get; }
protected IPresenter()
{
View = ...;
(View as dynamic).Presenter = this;
}
}
public interface IView<P>
{
P Presenter { get; set; }
}
public interface IEmployeeView : IView<EmployeePresenter>
{
void OnSave();
}
public class EmployeePresenter : IPresenter<IEmployeeView>
{
public void Save()
{
var employee = new EmployeeModel
{
Name = View.Bla
};
employee.Save();
}
}
class EmployeeView : IEmployeeView
{
public EmployeePresenter Presenter { get; set; }
public void OnSave()
{
Presenter.Save();
}
}
步骤如下:
- 实现
IEmployeeView
- 确保视图事件与其对应的 Presenter 方法相连
- 开始,像
new EmployeePresenter(...
。
3) 基于事件、观察者模式
在这种方式中,你可以像第一种方法一样将 Presenter 封装在 View 中(在 View 中实例化 Presenter),也可以像第二种方法一样将 View 封装在 Presenter 中(在 Presenter 中实例化 View)。但是根据我的经验,后者总是更加清晰易懂的设计。以下是一个例子:
public abstract class IPresenter<V> where V : IView
{
protected V View { get; }
protected IPresenter()
{
View = ...;
WireEvents();
}
protected abstract void WireEvents();
}
public interface IEmployeeView : IView
{
event Action OnSave;
}
public class EmployeePresenter : IPresenter<IEmployeeView>
{
protected override void WireEvents()
{
View.OnSave += OnSave;
}
void OnSave()
{
var employee = new EmployeeModel
{
Name = View.Bla
};
employee.Save();
}
}
class EmployeeView : IEmployeeView
{
public event Action OnSave;
void OnClicked(object sender, EventArgs e)
{
OnSave();
}
}
缺点:
- 您必须在视图和Presenter两侧连接事件-工作量加倍
涉及的步骤:
- 实现
IEmployeeView
- 确保从视图事件处理程序方法调用iview事件
- 确保iview事件成员从Presenter初始化
- 像
new EmployeePresenter()...
一样开始。
语言的限制有时会使设计模式更加困难。例如,如果C#支持多重继承,那么只需要一个抽象基本视图类,其中包含除UI特定组件以外的所有实现细节,然后由视图类实现即可。没有Presenter,经典的多态和非常简单!不幸的是,这是不可能的,因为.NET中的大多数视图类(如WinForms的Form
)已经继承自超级视图类。因此,我们必须实现一个接口并进行组合。此外,C#不允许您在接口实现中拥有非公共成员,因此我们被迫使IEmployeeView
中指定的所有成员都是公共的,这打破了视图类的自然封装规则(即视图项目中的其他视图可以看到与它们无关的EmployeeView
的详细信息)。无论如何,使用C#扩展方法的强大功能,可以采用更简单但非常有限的方法。
4) 扩展方法方法
这只是愚蠢的。
public interface IEmployeeView
{
void OnSave();
}
public static class EmployeePresenter
{
public void Save(this IEmployeeView view)
{
var employee = new EmployeeModel
{
Name = view.Bla
};
employee.Save();
}
}
class EmployeeView : IEmployeeView
{
public void OnSave()
{
this.Save();
}
}
缺点:
步骤:
- 实现
IEmployeeView
- 确保从视图事件中调用
this....
扩展方法
- 通过调用
new View...
启动工作流程
在所有的数字中,2和3看起来最好。