知识卡片
特殊情况:用同类型的替身消除到处判空
内容
空值天生会破坏多态——正常情况下可以放心对一个引用变量调用方法而不用管它具体是哪个子类,但一旦这个引用可能为空,就必须在使用前先判空,这类判空代码往往在程序里反复出现、大量冗余;作者提醒空值只是”特殊情况需要特殊处理”这类问题里最常见的一个例子,浮点数里的”无穷大”、身份不明的”居民”客户也是同类问题——本质都是某种类型的一般行为在特定情形下需要被替换。特殊情况模式的解法是创建一个专门的子类来承接这类特殊情形(比如给顾客类配一个”空顾客”子类),在这个子类里重载所有方法、提供一套无害的默认行为,之后凡是原本要返回空值的地方,都返回这个特殊情况对象代替——调用方拿到的始终是一个符合预期接口的正常对象,不再需要专门判空。因为通常不需要区分多个”空顾客”实例之间的差异,可以用享元模式实现(但不总是如此——比如某公共事业公司即使账单资料不全的”居民”客户,也需要各自累积欠费额度,就不能简单共享同一个空实例)。值得注意的是,”空”本身也可能有不止一种含义——”确实没有这个顾客”和”有一个我们无法确定身份的顾客”是两种不同的语义,应该考虑用不同的特殊情况子类分别表达,而不是笼统地塞进同一个”空顾客”类里。覆盖方法时还有一个常见的连锁套路:方法返回值本身也该是另一个特殊情况对象——比如向一个”身份不明的顾客”询问最后一份账单,合理的返回应该是一个”未知账单”对象,而不是又抛出一次空值判断的皮球。IEEE 754浮点运算里除以零返回NaN(而不是抛异常)、且NaN能继续参与后续算术运算,是这个模式在语言层面的经典应用范例。
参考来源
- 位置:《企业应用架构模式》第二部分"模式"之"第18章 基本模式"之"18.8.1 运行机制"(源文件:_epub-src/OEBPS/Text/000229.html)
- 结论依据:原文说明"'空'也可以有不同的含意。'空顾客'可能意指没有顾客,也可以意指有一个我们不认识的顾客。可以考虑使用不同的特殊情况子类来表示没有顾客或无法确定身份的顾客……当在'特殊情况'子类中对方法进行覆盖时,一个常用的套路是返回另一个特殊情况对象。例如,如果你询问某一无法确定身份顾客的最后一份账单,可能会得到一个'未知账单'对象",直接支撑本卡结论。
- 原始内容:返回一个"特殊情况",它具有与调用者原来的预期相一致的接口,而非返回空值或某个奇怪的值。