1.5.1 对抽象层次的权衡
回到毕加索的抽象画,如图1-8所示。如果映射到面向对象编程,抽象牛就是抽象类(Abstract Class),代表了所有牛的抽象。抽象牛可以被泛化成更多的牛,比如水牛、奶牛、牦牛等。每一种牛都代表了一类(Class)牛,对于每一类牛,我们可以通过实例化,得到一个具体的牛实例(Instance)。
图1-8 牛的抽象层次
从这个简单的案例中,我们可以总结出抽象的3个特点。
(1)抽象是忽略细节的。抽象类是最抽象的,忽略的细节也最多,就像抽象牛,只是几根线条而已。在代码中,这种抽象既可以是抽象类,也可以是接口(Interface)。
(2)抽象代表了共同性质。类代表了一组实例的共同性质,抽象类代表了一组类的共同性质。对于上面的案例来说,共同性质就是抽象牛的那几根线条。
(3)抽象具有层次性。抽象层次越高,其内涵越小、外延越大,也就是说它的含义越小、泛化能力越强。比如,牛就要比水牛的抽象层次更高,因为它可以表达所有的牛,水牛只是牛的一个种类。
而抽象的层次性主要涉及一个概念的外延和内涵,所以在进一步讲解抽象层次之前,我们有必要先理解一下外延和内涵的概念。
抽象是以概念(词语)来反映现实的过程,每一个概念都有一定的外延和内涵。概念的外延就是适合这个概念的一切对象的范围,而概念的内涵就是这个概念所反映的对象的本质属性的总和。例如“平行四边形”这个概念,它的外延包含着一切正方形、菱形、矩形及一般的平行四边形,而它的内涵包含着一切平行四边形所共有的“有四条边,两组对边互相平行”这两个本质属性。
一个概念的内涵愈广,则其外延愈狭;反之,内涵愈狭,则其外延愈广。例如,“平行四边形”的内涵是“有四条边,两组对边互相平行”,而“菱形”的内涵除这两条本质属性外,还包含着“四边相等”这一本质属性。“菱形”的内涵比“平行四边形”的内涵广,而“菱形”的外延要比“平行四边形”的外延狭。
内涵决定外延,但外延并不决定其内涵,比如“等边三角形”和“等角三角形”有相同的外延,但是却指向不同的内涵。外延和内涵也并非总是反向变化,事实并非如此,当内涵对其外延没有影响的时候,内涵的增加并不会导致外延的变小,比如“活着的人”“活着的不超过1000岁的人”。内涵虽然增加了,但其外延是一样的。[3]
抽象的层次性主要体现在概念的内涵和外延上,而这种层次性基本可以体现在任何事物上。比如一份报纸就存在多个层次上的抽象,“出版物”最抽象,其内涵最小,但外延最大,因为“出版物”不仅可以包含报纸,还可以包含书籍、期刊、杂志等。报纸的抽象层次如下。
• 第一层:一个出版物。
• 第二层:一份报纸。
• 第三层:《旧金山纪事报》。
• 第四层:5月18日的《旧金山纪事报》。
不同的抽象层次有不同的用途。当我要统计美国有多少种出版物时,就要用到最上面第一层“出版物”的抽象;如果我要查询旧金山5月18日当天的新闻,就要用到最下面第四层“5月18日的《旧金山纪事报》”的抽象。
对于程序员来说,对抽象层次的权衡是对我们设计能力的考验,要根据业务的需要,选择合理的抽象层次,既不能太高,也不能太低。
例如,现在要写一个关于水果的程序,我们需要对水果进行抽象,因为水果里面有红色的苹果,我们当然可以建一个RedApple的类,但是这个抽象层次有点低,只能用来表达“红色的苹果”。假如来一个绿色的苹果,你还得新建一个GreenApple类。
如图1-9所示,为了提升抽象层次,我们可以把RedApple类改成Apple类,让颜色变成Apple的属性,这样红色和绿色的苹果就都能用Apple表达了。再继续往上抽象,我们还可以得到水果类、植物类等。
图1-9 苹果的抽象层次
前面提到,抽象层次越高,内涵越小,外延越大,泛化能力越强。然而,其代价就是业务语义表达能力越弱。
具体要抽象到哪个层次,要视具体的情况而定,比如这段程序如果专门用于研究苹果,那么可能到Apple就够了;如果是卖水果的,则可能需要到Fruit;如果是做植物研究的,可能要到Plant,但很少需要到Object。
我经常开玩笑说:“为了通用性,把所有的类都设置为Object,把所有的参数都设置为Map的系统,是最通用的。”因为Object和Map的内涵最小,其泛化能力最强,可以适配所有的扩展。从原理上来说,这种抽象也是对的,万物皆对象嘛!但我们为什么不这么做呢?
这是因为,越抽象、越通用、可扩展性越强,其语义的表达能力就越弱;越具体、越不好延展,其语义表达能力却越强。所以,对于抽象层次的权衡是我们系统设计的关键所在,也是区分普通程序员和优秀程序员的重要参考指标。