如何在 D 接口中有意义地使用前置条件契约?
题
当我用“in”合约重写 D 中的函数时,会检查继承的“in”合约。如果失败,则会检查被覆盖的“in”合约。如果我没有在合同中指定任何内容,那么它会被解释为好像有一个空的“in”合同。所以下面的代码编译并运行成功。
module main;
import std.stdio;
interface I
{
void write( int i )
in
{
assert( i > 0 );
}
}
class C : I
{
void write( int i )
{
writeln( i );
}
}
int main()
{
I i = new C;
i.write( -5 );
getchar();
return 0;
}
我想要的前提只是 I.write()
当我打电话时要检查 i.write()
因为这就是静态已知的足以 I.write()
才能被编译器正确运行。检查所有先决条件 后 从面向对象的角度来看,动态调度让我觉得很奇怪,因为封装丢失了。
我可以重复前提条件或写 in { assert( false ); }
在所有实现该接口的类中,但这很痛苦。这是D语言的设计错误吗?或者有没有适当的可扩展方法来做到这一点?
解决方案
如果派生类中的函数重写其超类中的函数,则只需满足该函数及其基函数的契约之一。然后,重写函数就变成了放松契约的过程。
没有 in 契约的函数意味着函数参数的任何值都是允许的。这意味着,如果继承层次结构中的任何函数没有契约,那么重写它的函数的契约就没有任何作用。
相反,所有的外部契约都需要得到满足,因此压倒性的功能就变成了一个收紧外部契约的过程。
当多态行为受到质疑时,这实际上是一个困难的设计难题。例如,看看这个错误报告以及相关的长时间讨论: http://d.puremagic.com/issues/show_bug.cgi?id=6857
关于如何实现想要的行为的问题 - 当需要防止复制粘贴时,mixin 总是有效,但我不确定从“按合同设计”范式的角度来看是否可以这样做。不幸的是,需要有人在这个问题上更有理论能力的建议。
其他提示
D 中的前提条件是函数正确运行的要求。如果重载该函数,则为其编写新代码,旧的前提条件(旧代码的要求)不一定是新代码的要求。
所以这个问题虽然没有直接讨论接口,但 http://d.puremagic.com/issues/show_bug.cgi?id=6856
不过,这可能很难进入,沃尔特热衷于不进行重大改变。