在 Inning 中,可以看到无论构建器还是 event()方法都指出自己会“掷”出一个违例,但它们实际上没有那样做。这是合法的,因为它允许我们强迫用户捕获可能在覆盖过的event()版本里添加的任何违例。同样的道理也适用于 abstract 方法,就象在 atBat()里展示的那样。“ interface Storm”非常有趣,因为它包含了在 Incoming 中定义的一个方法—— event(),以及不是在其中定义的一个方法。这两个方法都会“掷”出一个新的违例类型: RainedOut。当执行到“ StormyInningextends”和“ implements Storm”的时候,可以看到 Storm 中的 event()方法不能改变 Inning中的 event()的违例接口。同样地,这种设计是十分合理的;否则的话,当我们操作基础类时,便根本无法知道自己捕获的是否正确的东西。当然,假如 interface 中定义的一个方法不在基础类里,比如 rainHard(),它产生违例时就没什么问题。
对违例的限制并不适用于构建器。在 StormyInning 中,我们可看到一个构建器能够“掷”出它希望的任何东西,无论基础类构建器“掷”出什么。然而,由于必须坚持按某种方式调用基础类构建器(在这里,会自动调用默认构建器),所以衍生类构建器必须在自己的违例规范中声明所有基础类构建器违例。StormyInning.walk()不会编译的原因是它“掷”出了一个违例,而 Inning.walk() 却不会“掷”出。若允许这种情况发生,就可让自己的代码调用 Inning.walk(),而且它不必控制任何违例。 但在以后替换从 Inning衍生的一个类的对象时,违例就会“掷”出,造成代码执行的中断。通过强迫衍生类方法遵守基础类方法的违例规范,对象的替换可保持连贯性。覆盖过的 event()方法向我们显示出一个方法的衍生类版本可以不产生任何违例—— 即便基础类版本要产生违例。同样地,这样做是必要的,因为它不会中断那些已假定基础类版本会产生违例的代码。差不多的道理亦适用于 atBat(),它会“掷”出 PopFoul—— 从 Foul 衍生出来的一个违例,而 Foul 违例是由 atBat()的基础类版本产生的。这样一来,假如有人在自己的代码里操作 Inning,同时调用了 atBat(),就必须捕获 Foul违例。由于 PopFoul 是从 Foul 衍生的,所以违例控制器(模块)也会捕获 PopFoul。最后一个有趣的地方在 main()内部。在这个地方,假如我们明确操作一个 StormyInning 对象,编译器就会强迫我们只捕获特定于那个类的违例。但假如我们上溯造型到基础类型,编译器就会强迫我们捕获针对基础类的违例。通过所有这些限制,违例控制代码的“健壮”程度获得了大幅度改善(注释③)。
③: ANSI/ISO C++施加了类似的限制,要求衍生方法违例与基础类方法掷出的违例相同,或者从后者衍生。
在这种情况下, C++实际上能够在编译期间检查违例规范。我们必须认识到这一点:尽管违例规范是由编译器在继承期间强行遵守的,但违例规范并不属于方法类型的一部分,后者仅包括了方法名以及自变量类型。因此,我们不可在违例规范的基础上覆盖方法。除此以外,尽管违例规范存在于一个方法的基础类版本中,但并不表示它必须在方法的衍生类版本中存在。这与方法的“继承”颇有不同(进行继承时,基础类中的方法也必须在衍生类中存在)。换言之,用于一个特定方法的“违例规范接口”可能在继承和覆盖时变得更“窄”,但它不会变得更“宽” —— 这与继承时的类接口规则是正好相反的。
P.275-P.276
这一段话及StormyInning.java例程需要好好看看。