代码之家  ›  专栏  ›  技术社区  ›  Scott Muc

如何使用MVP将服务层消息/错误传达给更高层?

  •  17
  • Scott Muc  · 技术社区  · 17 年前

    我目前正在从用户界面写一个ASP.NET应用程序。我正在实现MVP体系结构,因为我厌倦了WinForms,想要一些能够更好地分离关注点的东西。

    因此,对于MVP,演示者处理视图引发的事件。下面是一些我已经准备好处理用户创建的代码:

    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验证控件完成了主表单验证,但现在我需要验证数据是否充分满足服务层的标准。

    假设可以显示以下服务层消息:

    • 电子邮件帐户已存在(失败)
    • 输入的引用用户不存在(失败)
    • 密码长度超过了数据存储允许的长度(失败)
    • 已成功创建成员(成功)

    我们还可以说,更多的规则将出现在用户界面无法预料的服务层中。

    目前,如果事情没有按计划进行,我让服务层抛出一个异常。这是一个足够的策略吗?你们闻到这个密码了吗?如果我写了一个这样的服务层,你会因为不得不写这样使用它的演示者而感到恼火吗?返回码似乎太老套了,而bool的信息量还不够。


    不按OP编辑:合并作为OP答案发布的后续评论


    Cheekysoft,我喜欢ServiceLayerException的概念。对于我没有预料到的异常,我已经有了一个全局异常模块。你觉得让所有这些自定义异常都很乏味吗?我认为捕获基本异常类有点难闻,但不确定从那里进展如何。

    tgmbm,我喜欢lambda表达式的巧妙使用!


    感谢Cheekysoft的跟进。所以我猜如果你不介意用户被显示在一个单独的页面上(我主要是一个Web开发人员),如果不处理这个异常的话,这就是策略。

    但是,如果我想在用户提交导致错误的数据的同一视图中返回错误消息,那么我将不得不在演示者中捕获异常?

    以下是演示者处理ServiceLayerException时CreateUserView的外观:

    Create a user

    对于这种错误,最好将其报告给同一视图。

    不管怎样,我认为我们现在已经超出了我原来的问题范围。我将继续讨论你发布的内容,如果我需要进一步的详细信息,我将发布一个新问题。

    3 回复  |  直到 14 年前
        1
  •  15
  •   Community Mohan Dere    9 年前

    我听上去很合适。异常是可取的,因为它们可以从服务层内的任何地方被抛出到服务层的顶部,不管它在服务方法实现中嵌套得有多深。这样可以保持服务代码的整洁,因为您知道,呼叫演示者总是会收到问题通知。

    不捕获异常

    然而, 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)块以使用其他视图处理系统错误。

    我总是尝试将我的服务层看作一个独立的可分发库,并将其作为一个特定的异常抛出,这是有意义的。然后由演示者/控制器/远程服务实现决定是否需要担心特定的细节,或者只是将问题视为一般性错误。

        2
  •  3
  •   tgmdbm    17 年前

    正如Cheekysoft所建议的,我倾向于将所有主要异常转移到异常处理程序中,并让这些异常冒泡。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( ... );
    } );
    

    那么在错误中是空的,你可以走了。

    您可以进一步理解这一点,并使用处理 罕见的 例外情况。

        3
  •  1
  •   Cheekysoft Moz Morris    17 年前

    回答后续问题:

    至于创建异常变得乏味,你有点习惯了。使用一个好的代码生成器或模板可以在5或10秒内创建异常类,并且只需最少的手工编辑。

    然而,在许多现实世界的应用程序中,错误处理可以占到工作的70%,所以它实际上只是游戏的一部分。

    正如TGMDBM建议的那样,在MVC/MVP应用程序中,我让所有不可处理的异常冒泡到顶部,并被委托给异常处理程序的调度程序捕获。我将其设置为使用一个异常解析器来查找配置文件以选择一个合适的视图来显示用户。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>