2

我目前有一个命令处理接口,由几个不同的类针对不同的命令类型实现。我将装饰器模式与 IoC 容器(在我的情况下为 Unity)结合使用,为这些处理程序添加横切关注点,因此我创建了一些类,例如:

  • ValidatorCommandHandlerDecorator
  • LoggingCommandHandlerDecorator
  • AsyncCommandHandlerDecorator

这一切都按预期工作,实际上非常好。这里的潜在问题是每个装饰器实现都会检查接口的代码协定。我想通过只验证一次合同(最好在最外面的装饰器上)来潜在地避免这种情况。

是否有开箱即用的东西来实现这一目标?如果没有,您有什么建议来克服这个问题?

通用接口和合约类是这样的:

[ContractClass(typeof(CommandHandlerContracts<>))]
public interface ICommandHandler<TCommand>
{
    void Handle(TCommand command);
}

[ContractClassFor(typeof(ICommandHandler<>))]
internal abstract CommandHandlerContracts<TCommand>
    : ICommandHandler<TCommand>
{
    public void Handle(TCommand command)
    {
        Contract.Requires<ArgumentNullException>(
            command != null);
    }
}

ValidatorCommandHandler(作为我如何实现它们的一个例子)看起来像这样:

public sealed class ValidatorCommandHandlerDecorator<TCommand>
    : ICommandHandler<TCommand>
{
    private ICommandHandler<TCommand> m_decoratedHandler;
    private ICommandValidator<TCommand> m_commandValidator;

    public ValidatorCommandHandlerDecorator(
        ICommandHandler<TCommand> decoratedHandler,
        ICommandValidator<TCommand> commandValidator)
    {
        m_decoratedHandler = decoratedHandler;
        m_commandValidator = commandValidator;
    }

    public void Handle(TCommand command)
    {
        m_commandValidator.Validate(command);
        m_decoratedHandler.Handle(command);
    }
}

即使我创建了另一个仅用于装饰器的接口,它也必须继承原始接口并且仍然会检查合同,所以我不知道该怎么做。

这是 .NET 4.0 / VS2012,如果有帮助的话,我正在使用最新版本的代码合同。

4

3 回答 3

2

您在该接口上定义了一个合同,因此代码合同将确保合同得到满足;合同就是合同。我猜你担心性能问题,但是从性能的角度来看这应该不是问题,因为我希望代码契约编织的代码在这种情况下会非常快(只是一个空检查)。您可以使用 IL 反编译器来验证这一点。

但如果你真的想这样做,解决方案其实很简单。在查看您在做什么时,这个问题甚至看起来有点傻,因为您应该已经知道答案:

使用装饰器!

这是您新的最外层装饰器:

public class ContractCheckerCommandHandlerDecorator<TCommand>
    : ICommandHandler<TCommand>
{
    private readonly ICommandHandler<TCommand> decoratee;

    public ContractCheckerCommandHandlerDecorator(
        ICommandHandler<TCommand> decoratee)
    {
        this.decoratee = decoratee;
    }

    public void Handle(TCommand command)
    {
        if (command == null)
        {
            throw new ArgumentNullException("command");
        }

        this.decoratee.Handle(command);
    }
}

但是请注意,在使用 Unity 时,添加额外的装饰器来执行 null 检查的开销将远大于使用 Code Contracts 为每个实现添加 null 检查。在大多数情况下,使用 Unity 添加这个额外的装饰器时,开销可能仍然可以忽略不计。

于 2013-04-12T14:29:34.613 回答
2

如果当务之急是关于在每个调用的 Handle(ICommand) 上验证的合同。我会说这没关系,因为这是预期的,因为无法保证包装器将传递给装饰器链中的 wrap-pee 的内容,即使收到非空命令也是如此。

于 2013-04-12T17:49:30.910 回答
1

在 Code Contracts devlabs 论坛的这个线程中,我再次从 Manuel Fahndrich 那里得到了一个非常有趣的答案。

有一个属性可以放在成员上以设置特定的合约行为选项,这个属性用于禁用对 handle 方法的合约运行时检查,例如:

[ContractOption("runtime", "checking", false)]
public void Handle(TCommand command)
{ ... }

正如我在评论中所说,我将保持运行时检查,以防某些装饰器决定将 null 传递给它的装饰类(完全可能)。由于开销应该是最小的,我最终放弃了最初的想法。很高兴知道这样的事情是可能的,也许这个答案在搜索时对其他人有用。

于 2013-04-16T13:30:20.473 回答