几个月前跟师弟讨论关于Java和OO的话题,写下了一篇关于Java的文章谈了一下自己的体会,当时只写了第一部分关于语言的,打算第二部分写关于面向对象思想的,但是一直都没写。主要原因是觉得自己还没有想得很清楚,到底该怎样看待面向对象这样一种分析方法,及与其他方法的比较。

最近两三个月,尝试了相当多以前没接触过的的技术,linux,ios开发,arduino开发,在几个不同的平台上做目标不同的开发任务,反而对设计方法有了更多的思考。现在就可以再回过头来讲讲如何看待OOP了。

要评价一种方法,不能仅通过纯理论上的推断而下定论,而应该考虑它们实际的应用场景和产生的原因。无论是面向过程的设计,还是面向对象的设计,还是更高级的函数式编程或元编程,它们适用的场景和程度是不一样的,现实中我们往往是采取多种方法混合的方式,不同的方法解决不同的问题。

面向过程设计,顾名思义有一个过程。它应用的场景往往是由于有一个待解决的问题,通过原因分析和推断,我们研究出问题解决的方法,把它总结成解决的步骤,于是就有了过程式的描述。在很多算法的问题上,最后都是会归结为解决问题的步骤或者伪代码,这时面向过程的设计流程已经基本走完,剩下的就是把步骤变成代码而已。

而面向对象的思想,主要是出现在信息系统大规模地应用的时代,其面对的领域之广泛,软件开发的概念已不再是将现实问题抽象成问题和解题步骤这么简单了。在许多的软件项目中,我们面对的挑战包括有问题领域的深度和复杂度,需求的不确定性,项目人员的数量和能力水平,项目的生命周期管理等,这些难题成为了软件开发和使用过程中的最大障碍。有些软件从开始编码,到项目快要收尾了,需求还一直在改,导致代码极之混乱(如很多PHP项目);有些软件的问题领域模型非常复杂,需要一个良好的描述以清晰表达模型的结构/行为/关系,不再是“问题算法”这样的简单对应关系,而是现实工作流程的模拟;有些项目则是人员流动率非常大,三个月到大半年说不定就已经换了一批人,项目交接后还会由另一批人来进行维护,每个人都有可能把代码改得一塌糊涂,不知道会出现什么乱子;还有就是随着软件系统的向前发展,软硬件技术都可能会面临升级的问题,一个良好设计的系统必须想办法将技术升级的成本降到最低,否则软件系统很容易会进入无法升级的绝境。

实际上,我觉得面向对象的思想最大的好处是实现了两个目标:关注点分离(Separation of Concerns),以及将变更的影响局部化(Localize Changes)。

在Eric Evans《领域驱动设计》(Domain-Driven Design, 简称DDD)这本书里面,我们可以看到在与领域专家一起建立起一个关于问题领域的软件模型,是需要使用什么样的技巧才能帮助双方达成有效的沟通。在分析现实世界的过程中,我们把客观的事物变成了概念和词汇,通过语言描这些事物具有的属性,行为,以及事物之间相互影响的关系,如果没有一个语言能清晰地表达出这些关系,那么我们设计出来的软件就很可能忽略了其中一些细节,或者造成逻辑谬误,或者张冠李戴,或者遗漏了关键的步骤,这些都可能会使软件不可用(我们甚至还没谈及到交互设计上的可用性)。我们常见的分析工具包括有概念图(Concept Map),思维导图(Mind Map)等,而面向对象的方法只不过是相似地把这个分析过程转化为软件模型的描述而已。

在面向对象的分析过程里,我们主要会考虑类的抽象,属性,行为,类之间的依赖关系和映射关系等,在DDD方法学里面所提倡的一种方式就是用领域的词汇来作为类设计的词汇,属性,方法名都可以用现实的行为去代替。比如酒店订房系统,可以抽象出来的类有Person, Room, BookingService,行为就包括了有bookRoom, checkinRoom, checkoutRoom等。这体现了面向对象设计的过程其实就是我们思考分析现实问题模型的过程,OO的分析方法成为了我们最好的对现实世界的抽象和描述。为什么说它很好地实现了关注点分离(Separation of Concern)呢?因为当我们能够很好地识别出相应的类时,我们就可以很清晰地看到,什么样的动作应该由谁去触发,什么样的数据/信息应该找谁去要,每一个对象的职责范围都很明确,把不属于它们的东西分离了开去。在我们做详细设计的时候,通过关注点的分离,我们就可以把注意力集中在要分析的问题上,避免了过度发散,或者被信息间的依赖关系绕昏了头脑。

同时,通过对象抽象出来的接口/协议,我们相当于制定了对象之间通信的方式,这使得部件之间进行替换变得相当容易。比如说Adapter的设计模式,它可以帮我们把不同的底层实现,通过封装实现了相似的功能,那么我们替换不同的底层实现时,只需要更改Adapter就可以了。无论你是MP3,手机,平板,还是读卡器,移动硬盘,u盘,只要你能插USB,你就可以被计算机用来传输数据。这样,当我们考虑软硬件技术升级的时候,或者内部逻辑有更改的时候,我们有很多东西就可以考虑在某一个接口范围内进行修改/替换,只要在外面看起来的是一样的,这个变更的影响就会被Localized了。而面向对象的好处是,由于每个类设计的职责分明,你总能找到某一个合理的范围内进行修改,而不怕你的更改造成无法控制的影响。

可以毫不客气地说,Java语言就是为了这样的面向对象方法学而诞生的,它的语言特性里面处处体现了这些面向对象的指导思想。它并不在乎类或层级的多少,而更在乎于你对软件模型的分析是否清晰,合理,职责分明,因为在面对软件复杂性的时候,正确性比性能更重要;性能所带来的成本,可以通过可用性所带来的规模效应来分摊。但Java在应用面向对象方法的时候也有它的缺点,由于它过于强调面向对象,有时会造成不必要的过度设计,比如被人诟病的数十层calling stack,一大堆VO, DTO, PO数据对象,每一个问题无论大小都必须通过类/方法来解决,使得运用起来不太灵活。因此,很多人往往倾向于选择像python这样同时兼顾有OO的思想和自由的编写方式的语言。无论怎样,一旦面对问题领域的复杂性的时候,面向对象方法还是会成为了最重要的分析工具之一。

至于函数式编程和元编程,是立足于更高的抽象层次去解决问题的方法学,本人理解不是很深,只举一个简单的例子来说一下函数式编程——Google的MapReduce框架。其实这两种抽象的设计方法,都属于对软件过程本身的抽象。所谓元编程,就是对编程本身进行抽象,比如像MDA,它可以让你定义平台无关的编程元模型,然后通过编写模型描述和映射关系,生成平台相关的具体某种语言的程序代码。函数式编程如MapReduce,它把所有不同问题域的数据挖掘操作,最后都抽象为Map方法和Reduce方法两个过程,然后就基于这两个方法,写一套可以实行分布式计算的框架,任何数据挖掘的开发人员只要能够定义出其相应的Map函数和Reduce函数,就能够跑在这个MapReduce框架上进行可扩展的数据挖掘计算。由于这些编程方法的高度抽象,使得它的应用范围会很广,但在应用的过程之中还要作一个具体化过程的定义,才可以发挥它们的作用。

以上写的属于对一些软件设计方法上的思考,下一篇打算再谈谈软件工程方面的话题。方法学选用的宗旨是,没有绝对的好与差,只有应用场景的区别。看了此文的读者,也希望你们能够结合自己的思考来选择自己合适的设计方法。

		分享: