深入思考面向对象,静态抽象+动态抽象

49 阅读5分钟

在不同的编程语言中,都或多或少地实现了面向对象这个特性。面向对象如此特别,以至于在介绍 Java/C#/C++ 这些编程语言时,都得加上“面向对象编程语言”这样的头衔。与编程语言本身对面向对象的重视形成对比的是,不少业务开发者却不太了解面向对象。 这样的反差是由什么原因造成的?其中隐藏的信息值得我们思考。

面向对象在业务中的缺失和面向对象思维的缺位有关,面向对象是一种逻辑抽象能力。它要求我们思考代码的静态位置分布(即静态抽象),也让我们考虑代码间动态交互的稳定性(即动态抽象)。

面向对象思维

面向对象和解决代码组织分布混乱有关系。代码的组织没有固定的形式:代码既可以全部放置到同一个文件;也可以给每个程序函数都新建一个文件,来让文件数量爆炸;还可以把这两者适度折个中,文件数量不多不少。但这个“折中”的程度具体是多少,谁也没法在脱离实际情况下说清楚。文件和代码间极弱的相关性,在我们吸收理解现存代码时会带来很大的负担。能否消减这些负担,简化我们的理解成本?

好理解的代码有良好的分类,更有良好的逻辑性。 我们常会犯这样的错误:业务需求还在脑海中影影绰绰,整体流程才略有头绪,自己就急不可耐起来,匆匆地动起手写代码。这样写出的代码往往容易出现词不达意,前后不一致的情况。要避免这些问题,写代码前多做思考:先不动手,先“动头”。想想业务流程是怎样的,流程运作过程中又要和哪些对象交互(面向对象中的“对象”就是这样出来的)。

代码分类是面向对象编程重要的组成部分,但面向对象编程只是表象,是面向对象思维落地到代码上的简陋“抓手”。这个抓手和背后的面向对象思维差得太远了,它如此简陋,以至于很难想象它能有解决逻辑抽象这样的大用处。可是现实生活就是这样,此刻总是没有完美的方案,只有“这一步”方案。但无论如何,踏出“这一步”才是当前最重要的。

代码分类这样的“这一步”、这样的临时方案,是要在编码流程上给我们一个限制,即使限制的力度很弱。它让我们在写一段代码前总要思考一个问题:这个方法该放到哪个类?它是属于哪一个对象?如果我们能抓住机会,在编码前做够了思考,这些问题很快能有答案。但如果没有思考,可能就得抓耳挠腮。

有读者可能会奇怪:自己 Java/C#/C++ 用得不少,但从没纠结过某段代码该放到哪,这又是为什么?这正体现了面向对象编程这个“抓手”的简陋之处:我们既可以规规整整、清清楚楚地逐个思考有哪些业务对象;也可以搞得模模糊糊的,找一些假大空的全能词组,比如:Controller、Service、Model,代码就只分这三类。这也可以把代码写下来,面向对象编程本身可没有不让你写的能力。

代码分类在面向对象思维中举足轻重,但它不止于此,面向对象思维更是要鼓励我们我们做好行为的抽象,减少复杂行为间的相互干扰。抽象无处不在,做“事前思考”、整理对象分类是静态抽象,理清对象的行为则是动态抽象。这很像项目开发,先组织静态的团队角色,一个团队得有:产品、开发、设计、运营。有了团队后要确立各角色间动态交互的标准化流程,避免在工作时各角色间相互干扰。

静态抽象关注封装和继承:对象有哪些内容,这些内容的凝聚力够不够,能不能抽象成一个整体。抽象出的整体间有怎样的关系?会不会需要相互依赖、相互复制?是不是父子对象关系?

动态抽象关注多态。代码是要运作的,对象之间渴望交互。交互可以抽象为标准化的契约,但交互千变万化,这些多种多样的变化怎么和死板的契约联系起来?

对于动态抽象,以不变应万变是一种有效的方法。某种交互下的对象是多态的,不过这些交互都可以归纳在一个类别下,都能形成同一种契约。所以,如果我们能抽象出在动态变化的逻辑前的入口,把这个入口固定成这个交互的契约,便能收敛交互的复杂性。

放一张图,侵权请联系删除

面向对象本质上是一种思维模式,是逻辑抽象。这个抽象分为静态和动态两个部分,静态抽象让我们思考代码到哪去,动态抽象让我们思考怎么把变化的代码关在抽象的笼子里。