如上图所示,规约就好像一道防火墙一样,将客户和实现者隔离开。它使得客户不必知道这个单元是如何运行的,也使得实现着不必管这个单元会被怎么使用。这种隔离造成了“解耦”,客户自己的代码和实现者的代码可以独立发生改动,只要双方都遵循规约对应的制约。
规约是团队工作中的关键点。如果没有规约,就没有办法分工实现各种方法。规约就像一份合同:实现者的义务在于满足合同的要求,客户依赖这些要求工作。事实上,我们会发现就像真的合同一样,规格说明对双方的制约:当合同上有前置条件时,客户有责任满足这些条件。
一个规约说明含有以下两个条款:
前置条件:关键词是requires
后置条件:关键词是effects
前置条件是客户的义务(谁调用的这个方法)。它确保了方法被调用时所处的状态。
后置条件时实现者的义务。如果前置条件得到了满足,那么该方法的行为应该符合后置条件的要求,例如返回一个合适的值,抛出一个特定的异常,修改一个特定的对象等等。
如果前置条件不满足的话,实现也不需要满足后置条件——方法可以做任何事情,例如不终止而是抛出一个异常、返回一个任意的值、做一个任意的修改等等。
如果想要改变一个方法——不管是它的实现方法还是规约本身。并且现在已经有使用者在依赖之前的规约来使用方法了,此时该怎么确定新的规约可以安全的替换原有的规约呢?
定义:规约S2强于规约S1,如果:
1.S2的前置条件弱于或等价于S1
2.S2的后置条件强于或等于S1的后置条件。
如果S2强于S1,那么任何S2的实现方法都可以拿来实现S1。并且在程序中可以安全的用S2的模块替换S1模块。
这两个条件实际上表现得是:可以弱化前置条件(即更好满足前置条件),这会让使用者的限制更少,也可以强化后置条件(即返回更清晰,更有保证性)。
例如
可以被替换为:
下面的规约前置条件更弱,但后置条件更强,这就是一个强规约替换弱规约的例子。
对于一个更强的规约说明,它在图示中能描绘的实现范围更小,一个更弱的规约说明的实现范围更大。
一个好的规约会清晰明确的要求实现者和使用者遵守相关的制约。而Bug经常是因为实现者和使用者对于接口的理解冲突导致的,规约会明显减小这种可能性。在模块中使用一些能够交由机器检查的特性,例如静态检查、异常等而不是注释会进一步降低bug的可能性。