
1.5.3 强制类型转换中的抽象层次问题
面向对象设计中有一个著名的SOLID原则,它是由Bob大叔(Robert C.Martin)提出来的,其中,L代表LSP,即Liskov Substitution Principle(里氏替换原则)。简单来说,里氏替换原则就是子类应该可以替换任何父类会出现的地方,并且经过替换以后,代码还能正常工作。
思考一下,我们在写代码的过程中,什么时候会用到强制类型转换呢?当然是LSP不能被满足的时候,也就是说子类的方法超出了父类的类型定义范围,为了使用子类的方法,只能使用类型强制转换将类型转成子类类型。
举个例子,在苹果(Apple)类上,有一个isSweet()方法用于判断水果甜不甜;在西瓜(Watermelon)类上,有一个isJuicy()用于判断水分是否充足的;同时,它们都共同继承一个水果(Fruit)类。
此时,我们需要挑选出甜的水果和有水分的西瓜,会编写如下一段程序:


因为pickGood()方法的入参的类型是Fruit,所以为了获得Apple和Watermelon上的特有方法,我们不得不使用instanceof做一个类型判断,然后使用强制类型将其转换为子类类型,以便获得它们的专有方法,很显然,这违背了里氏替换原则。
问题出在哪里呢?对于这样的代码,我们要如何去优化呢?仔细分析一下,可以发现,根本原因在于isSweet()和isJuicy()的抽象层次不够,站在更高的抽象层次,也就是Fruit的视角看,我们挑选的就是可口的水果,只是具体到苹果时,我们看甜度;具体到西瓜时,我们看水分而已。
因此,解决方法是对isSweet()和isJuicy()进行抽象层次提升,在Fruit上创建一个isTasty()的抽象方法,然后让苹果和西瓜类分别去实现这个抽象方法就好了,如图1-12所示。

图1-12 提升抽象层次
下面是重构后的代码,通过提升抽象层次,我们消除了instanceof判断和强制类型转换,让代码重新满足了里氏替换原则,也使代码重新变得优雅了。


所以,每当在程序中准备使用instanceof做类型判断,或者用cast做强制类型转换的时候,再或者程序不满足LSP的时候,我们都应该警醒一下:好家伙!这又是一次锻炼抽象能力的绝佳机会。