1

更多地了解代码合约,它们似乎是我想在我的项目中使用的东西。

鉴于我构建 Web 服务层(从 MVC 控制器访问的服务层的抽象)的方式,我发现自己想知道为什么不能为接口实现的各种排列指定不同的合同。

具体来说,我对在给定通用接口方法的情况下只能以 1-1 方式指定合同的原因感兴趣。

这是我的代码结构的一个示例,我的目标是确定我想如何使用代码合同。我相信有更多经验的人能够将我推向正确的方向。

我正在使用 CQRS 风格的方法,例如:

public interface IQuery<in TInput input,out TOutput output>
{
     TOutput Invoke(TInput request)
}

public interface IGetSomeUnicornsFromAMagicalLand : 
                                             IQuery<int, IEnumerable<Unicorn>>{}

 // Implementation
public class GetSomeUnicornsFromMagicLand : IGetSomeUnicornsFromAMagicalLand
{
    public IEnumerable<Unicorn> Invoke(int numberOfUnicornsToReturn)
    {
       // Here I'd like to specify some preconditions on the input, 
       // specific to type int

       return _wizardry
                 .GetMagicCreature<Unicorn>(numberOfUnicornsToReturn)
                 .DoMagicalConversionToEnumerable()
    }

}

鉴于这种情况,希望在实现级别而不是在设计用于在接口上应用合同的抽象类(作为通用机制)中指定合同似乎是合理的。

  • 无法做到这一点的一些原因是什么?
  • 还有其他方法可以解决这个问题吗?
  • 如果我想使用代码合同,这不是一个好的结构吗?
4

1 回答 1

2

根据Liskov Substitution Principle,总的来说,我会说在接口级别指定这些合同确实更有意义。

LSP 基本上说抽象概念的每个实现都应该是可互换的,而抽象概念的用户不必知道它们的区别。换句话说,您的代码不应该因为您切换到某个接口的另一个实现而开始中断。

通过对输入参数施加特定于实现的附加要求,您可以有效地将调用代码(在您的情况下,这很可能是处理 的一般概念的框架级代码IQuery)与特定实现耦合。

对于您的具体情况,推理可能适用也可能不适用(这就是为什么它是一个原则,它不是一成不变的),但显然像代码合同这样的一般框架只能考虑一般原则:-)

编辑:在考虑了更多之后,在我看来,派生接口可能是“真正的”接口,而不是通用接口IQuery<TInput, TOutput>。通用基础接口似乎更像是一个“实现细节”,您确实不能对这个通用基础接口做出任何有意义的假设(合同)。只有在具体的层面上,你才能对这些输入和输出参数做出任何有意义的推理。

也许我们需要 C# 中 C++ 的私有继承(或实现继承)来对消费者“隐藏”这个接口。阅读相关问题的出色答案。它还谈到 Liskov 可替代性在这种情况下是“谎言”,我倾向于同意这一点。

于 2012-07-25T08:48:29.853 回答