我目前正在从UI编写ASP.Net应用程序。我正在实施一个MVP架构,因为我厌倦了Winforms并希望能够更好地分离关注点。
因此,对于MVP,Presenter处理由View引发的事件。以下是我处理用户创建的一些代码:
public class CreateMemberPresenter
{
private ICreateMemberView view;
private IMemberTasks tasks;
public CreateMemberPresenter(ICreateMemberView view)
: this(view, new StubMemberTasks())
{
}
public CreateMemberPresenter(ICreateMemberView view, IMemberTasks tasks)
{
this.view = view;
this.tasks = tasks;
HookupEventHandlersTo(view);
}
private void HookupEventHandlersTo(ICreateMemberView view)
{
view.CreateMember += delegate { CreateMember(); };
}
private void CreateMember()
{
if (!view.IsValid)
return;
try
{
int newUserId;
tasks.CreateMember(view.NewMember, out newUserId);
view.NewUserCode = newUserId;
view.Notify(new NotificationDTO() { Type = NotificationType.Success });
}
catch(Exception e)
{
this.LogA().Message(string.Format("Error Creating User: {0}", e.Message));
view.Notify(new NotificationDTO() { Type = NotificationType.Failure, Message = "There was an error creating a new member" });
}
}
}
我使用内置的.Net验证控件完成了主表单验证,但现在我需要验证数据是否足以满足服务层的标准。
假设以下服务层消息可以显示:
我们还要说,UI无法预见的服务层中会有更多规则。
目前,如果事情没有按计划进行,我的服务层会抛出异常。这是一个足够的策略吗?这段代码对你们有异味吗?如果我写了这样的服务层,你会不得不编写以这种方式使用它的Presenters吗?返回代码看起来太旧了,bool只是没有足够的信息。
不是由OP编辑:合并后续评论,作为OP的答案发布
Cheekysoft,我喜欢ServiceLayerException的概念。我已经有一个全局异常模块用于我不期望的异常。你觉得让所有这些自定义异常变得单调乏味吗?我认为捕获基础Exception类有点臭,但不确定从那里取得进展。
tgmdbm,我喜欢在那里聪明地使用lambda表达式!
感谢Cheekysoft的后续行动。因此,如果您不介意用户显示单独的页面(我主要是Web开发人员),如果不处理异常,我猜这将是策略。
但是,如果我想在用户提交导致错误的数据的同一视图中返回错误消息,那么我是否必须在Presenter中捕获异常?
以下是Presenter处理ServiceLayerException时CreateUserView的样子:
对于这种错误,最好将它报告给同一个视图。
无论如何,我认为我们现在超出了我原来问题的范围。我会玩你发布的内容,如果我需要更多详细信息,我会发布一个新问题。
答案 0 :(得分:15)
这听起来对我来说很合适。异常是可取的,因为它们可以从服务层内的任何位置抛到服务层的顶部,无论它在服务方法实现中嵌套有多深。这使得服务代码保持干净,因为您知道调用演示者将始终获得问题的通知。
不要捕捉异常
但是,演示者中的don't catch Exception,我知道它很诱人,因为它会使代码更短,但是您需要捕获特定的异常以避免捕获系统级异常。
规划简单的例外层次结构
如果要以这种方式使用异常,则应为自己的异常类设计异常层次结构。 在最小值创建一个ServiceLayerException类,并在出现问题时在服务方法中抛出其中一个。然后,如果您需要抛出一个异常,该异常应该/可以由演示者以不同方式处理,您可以抛出ServiceLayerException的特定子类:例如,AccountAlreadyExistsException。
然后,您的演示者可以选择
try {
// call service etc.
// handle success to view
}
catch (AccountAlreadyExistsException) {
// set the message and some other unique data in the view
}
catch (ServiceLayerException) {
// set the message in the view
}
// system exceptions, and unrecoverable exceptions are allowed to bubble
// up the call stack so a general error can be shown to the user, rather
// than showing the form again.
在您自己的异常类中使用继承意味着您不需要在演示者中捕获多重异常 - 如果需要,您可以 - 并且您最终不会意外地捕获您无法处理的异常。如果您的演示者已经位于调用堆栈的顶部,请添加一个catch(Exception)块以使用不同的视图处理系统错误。
我总是试着将我的服务层视为一个单独的可分发库,并将其视为具体的异常。然后由演示者/控制器/远程服务实现决定是否需要担心具体细节或仅将问题视为一般错误。
答案 1 :(得分:3)
正如Cheekysoft建议的那样,我倾向于将所有主要的异常移到ExceptionHandler中,并让这些异常冒泡。 ExceptionHandler将为异常类型呈现适当的视图。
然而,任何验证异常都应该在视图中处理,但通常这种逻辑对于应用程序的许多部分是通用的。所以我喜欢这样的帮手
public static class Try {
public static List<string> This( Action action ) {
var errors = new List<string>();
try {
action();
}
catch ( SpecificException e ) {
errors.Add( "Something went 'orribly wrong" );
}
catch ( ... )
// ...
return errors;
}
}
然后在致电您的服务时,请执行以下操作
var errors = Try.This( () => {
// call your service here
tasks.CreateMember( ... );
} );
然后错误是空的,你很高兴。
您可以更进一步,并使用处理罕见例外的自定义异常处理程序进行扩展。
答案 2 :(得分:1)
回答后续问题:
至于创建异常变得单调乏味,你就会习惯它。使用优秀的代码生成器或模板可以在大约5或10秒内创建具有最少手动编辑的异常类。
然而,在许多现实世界的应用程序中,错误处理可能是工作的70%,所以它只是游戏的一部分。
正如tgmdbm建议的那样,在MVC / MVP应用程序中,我让所有不可处理的异常冒泡到顶部并被委派给ExceptionHandler的调度程序捕获。我进行了设置,以便它使用ExceptionResolver查看配置文件,以选择适当的视图来显示用户。 Java的Spring MVC库做得非常好。这是Spring MVC的异常解析器配置文件的片段 - 它用于Java / Spring但你会明白这一点。
这完全会占用演示者/控制器的大量异常处理。
<bean id="exceptionResolver"
class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<prop key="UserNotFoundException">
rescues/UserNotFound
</prop>
<prop key="HibernateJdbcException">
rescues/databaseProblem
</prop>
<prop key="java.net.ConnectException">
rescues/networkTimeout
</prop>
<prop key="ValidationException">
rescues/validationError
</prop>
<prop key="EnvironmentNotConfiguredException">
rescues/environmentNotConfigured
</prop>
<prop key="MessageRejectedPleaseRetryException">
rescues/messageRejected
</prop>
</props>
</property>
<property name="defaultErrorView" value="rescues/general" />
</bean>