代码之家  ›  专栏  ›  技术社区  ›  Jamie Dixon

代码契约和接口实现-1-1关系的一些原因是什么

  •  1
  • Jamie Dixon  · 技术社区  · 12 年前

    了解更多关于代码契约的信息,它们似乎是我想在项目中使用的东西。

    考虑到我构建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()
        }
    
    }
    

    在这种情况下,似乎有理由在实现级别指定契约,而不是在设计用于在接口上应用契约的抽象类中(作为一种通用机制)。

    • 不能做到这一点的原因是什么?
    • 是否有其他方法可以满足这一需求?
    • 如果我想使用代码契约,这不是一个好的结构吗?
    1 回复  |  直到 12 年前
        1
  •  2
  •   Community CDub    7 年前

    鉴于 Liskov Substitution Principle ,一般来说,我认为在接口级别指定这些契约确实更有意义。

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

    通过在输入参数上强加特定于实现的附加要求,您有效地耦合了调用代码(在您的情况下,这很可能是处理 IQuery ),具体实施。

    对于你的具体情况,推理可能适用也可能不适用(这就是为什么它是一个原则,不是板上钉钉的),但很明显,像代码契约这样的通用框架只能考虑一般原则:-)

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

    也许我们需要C++在C#中的私有继承(或实现继承)来对消费者“隐藏”这个接口。 Read this excellent answer to a related question 。它还谈到,在这种情况下,利斯科夫的可替代性是“谎言”,我倾向于同意这一点。