3

我试图通过将步骤(验证、附加相关内容、格式、发送)划分为可以更容易测试、记录和更新的单独类来重构一些“发送电子邮件”代码。

作为其中的一部分,我必须想办法让操作将验证或暂时错误(“该项目已删除”)传回发起者,以便它可以向用户询问更多信息或告诉他们坏消息. 这是线程采用的路径(是的,涂鸦)

     "Controller"                     
      .   -> Outbox 
      .         -> Validator 
      .         -> Formatter 
      .         -> Sender
      .   <- 

                   -> Parameters, work in progress
                   <- Good, not so good, "you better sit down" news

所以你是一个深思熟虑的人,在“返回”、“例外”或“背景”之间……哪一个让你最开心?

A. 在任何问题上抛出异常,让控制器划分它可以优雅处理的和知道我的“蜂鸣器”的那些。

B.返回某种 Result<T>类来携带操作的产品(电子邮件)和各种操作的枚举结果。

C. 将上下文传入/传出所有步骤,在这些步骤中可以指示他们无法处理的任何参数,并保持方法签名非常简单。

D. 儿子,你想多了。这就是你要做的事情:<YourSpecialJujuHere/>

感谢您的所有贡献,你们一起摇滚。

4

2 回答 2

4

您可以将模板方法模式与策略模式一起使用:

您的控制器成为模板方法。对于电子邮件发送过程的每个步骤,您都调用了实现该步骤的委托/策略类。

public class EmailSender
{
    private iOutboxGetter outboxGetter;
    private iMsgValidator validator;
    private iMsgFormatter formatter;
    private iMsgSender    sender;

    //setters for each stragegy, or a constructor
    //good use for IOC container

    public iSendResult SendMessage(iMsgParams params)
    {
        try
        {
            var outbox = outboxGetter.getOutbox(params.outbox);
            var validationResults = validator.validate(params);
            if(validationResults.IsValid)
            {
                var msg = formatter.formatMsg(params.message);
                sender.send(msg);
                return new AllGoodSendResult();
            }
            else
            {
                return new ValidationFailedSendResult(validationResults);
            }
        } 
        catch(CatastrophicException e)
        {
           Pager.SendCriticalPage(e.message);
            return new CatistrophicFailureSendResult(e);
        }
    }
}

当代码必须偏离快乐路径时,我更喜欢使用异常。我觉得他们将逻辑和错误处理完全分开。

编辑: SendMessage 方法的返回向调用者指示验证是否通过,以及验证失败的原因。然后,调用者可以提示用户提供更多信息并重试,或指示成功。只有在真正异常的情况下才会抛出异常。

使用这种方法,您的算法的每个组件都可以被独立地模拟和测试,并且没有 Strategy 需要知道任何其他 Strategy 是如何工作的,也不需要知道如何处理其他人的错误。最后,您所有的错误处理都集中在一个地方。

于 2009-11-03T18:39:15.360 回答
1

也许问题在于动作的顺序,这使得动作调用成为下一个。

另一种方法是让 Controller 依次调用所有操作。在这种情况下,您的控制器与每个操作之间存在直接关系。

每个动作都可能返回一个简单的结果,或者通过适合其情况的方式发出错误信号:

  • 通过异常处理异常情况
  • 通过 null 返回没有结果。
  • ...

从一个动作到另一个动作的重用可以作为局部变量发生。

示例代码(根据需要添加参数等):

    class Controller1 {

       private Sender sender = new SenderImpl();

       public void process(String text) {
         try {
           Outbox box = getOutbox();
           List<Errors> errors = validate(text);
           if (!errors.isEmpty()) {
             ....
             return;
           }
           String formatted = format(text);
           sender.send(formatted);
         } catch(MyException e) {
           ....
         }
       }
    }

虽然在这段代码中,这些步骤被委托给同一个类的方法,但很容易与其他类的实例遵循相同的结构(但仅在需要时,不要过度设计)。正如您所提到的,这可以证明可测试性是合理的。我将示例代码更改为sender.

于 2009-11-02T17:47:20.713 回答