面向对象的特点是什么?这绝对是一个出现频率很高的初级面试题。为什么说很多参加了很多年工作的coder用面向对象的语言写出来的代码还是面向过程的?到底他们本质的区别在哪里?
【背景】
很多初级的工程师也许会背什么是面向对象的特点,但是切身的体会,可能需要在经过大量的编码之后,才会有些感觉。
这里我们先从整个软件的发展历程来看,对我们理解面向对象应该会有帮助。
脱离了打孔穿带,汇编这样的低级语言之后,程序员真正得以把大量的时间和精力放在真正需要解决的问题上。这个时期软件编程的特点,基本就是遇到什么问题,写个逻辑干它就完事。基本上就是把需要交给机器做的事情,按照流程,第一步干嘛,第二步干嘛...最后一步干嘛,码下来。这种模式就是面向过程。它直观,符合我们解决问题时思维的模式,所以在跳过了一开始的低级语言之后,很自然地就到了这个阶段,这也是一开始写代码的Coder正常的思维模式。
就这样发展下去不也挺好么?软件开发的发展历程给出的答案是:不好。面向过程虽然符合我们解决问题时的思维模式,但是当我们需要解决的问题越来越复杂,越来越庞大,这个面向过程的编程模式带来了巨大的问题。无论水平高低,无论管理是否优秀,始终会存在这些问题,随着规模越大,软件的质量越低下,进度也得不到保障。人们会发现,当一个过程被固定下来成为函数时,此时如果过程或者被操作的客体发生了变化,系统中许多相关的操作都会产生变化,这其实就是上述问题的一大原因。这一度成为软件发展的巨大障碍,也直接催生了后来的软件工程,但即便软件工程也无法有效地解决这个问题。这一时期,诞生了被后人视为神作的《人月神话》,就是在总结基于这种面向过程开发的一个超级项目的工程经验。
后来,人们发现可以对需求或者编程工作进行分治,也就是模块化的思想。划分模块虽然没有进入到面向对象的程度,但是已经在整个软件开发过程中发挥了作用。从开始关注软件开发模式的问题开始,人们就不断地在开发过程中尝试、总结经验。如何才能做到代码的可扩展、易维护、可重用等来切实提升软件开发的效率以及健壮性。到C++出现,面向对象才开始流行起来,到了1994年,GOF四人帮提出了设计模式,再到后来,JAVA和C#将面向对象这一思想推到了巅峰。
其实看了上述的历程,我们知道面向对象的出现,其实是为了解决软件开发过程中的问题。尤其是解决如何在需求复杂,规模庞大,人员众多的项目上,提升软件开发的质量和效率的问题。它是基于人们长期的经验总结,是一种实践过程的产物,在众多的解决方案中脱颖而出的一种软件编程方法。
【什么是面向对象】
在了解了这个背景之后,我们来看看什么是面向对象。
面向对象是一种编程思想,一种编程方法。是业界在解决需求复杂、规模庞大的软件开发过程中所遇到的各种复杂的变化问题中,经过长时间实践检验出来的一套软件开发方法。所谓的对象,就是一个模型,这个模型包含属性和方法,简单说,对象就是一组数据和处理这些数据的方法所组成的一个模型。在面向对象的软件开发过程中,我们需要把所有的事物都抽象成对象,将它们统一设计成对象这种模型,既是我们所说的面向对象编程。
在程序员这个群体中,大家经常调侃自己是面向工资编程、面向领导编程、面向简历编程......发现了吗?虽然是个调侃,但是这个面向什么,其实意思就是以什么为核心来进行编程。所以面向对象编程,就是以对象这种模型为核心,来开展我们的开发工作。
在一个复杂系统的需求抛来之后,第一件事情,我们先屡清楚这里面有什么客体,包含什么人、什么物、什么组件等,我们将它们的属性列出来;继而分析在整个系统中,它们各自的功能是什么,从而将它们的方法也定义出来从而形成了各种各样的对象。这其实就是典型的面向对象的需求分析方法。
【面向对象如何发挥作用】
发挥什么作用呢?简单来说,就是简化复杂的事物。
如果面对一个复杂的需求,我们在开发过程中使用面向对象这种方法简化了整个需求,化繁为简,那就是发挥了作用。
而在面向对象的发展过程中,人们在实践中总结了一套行之有效的方法,并将这些方法抽象成了理论,用于更为广泛地指导软件开发人员如何让面向对象这种开发方法尽可能地发挥出它的优势。简要地说,就是五大原则:"单一职责原则"、"开放封闭原则"、"里氏替换原则"、"依赖倒置原则"、"接口分离原则"、"迪米特原则(高内聚低耦合)",并在此基础之上,发展出了几十种设计模式。这些内容限于篇幅在此不展开,相关的内容网上的资料也很多,大家可以自行搜索。
这些原则以及几十种设计模式,让我们在面对复杂多变的需求、庞大的开发规模这些难题时,有了一套行之有效的办法,将整个软件开发的复杂度控制在一定范围内,并大大加强了软件编码的可扩展性,他们是开发人员面对各种复杂场景时,利用面向对象这一思想进行化繁为简的最佳实践。
【三大特性】
这是个回避不了的问题。
在上文提到了面向对象的基本原则以及衍生出来的设计模式中,我们会发现面向过程所不具备,而这些模式中处处使用到的设计技巧中,最突出的就是封装、继承与多态。所以说,这三个特性,就是面向对象的最为核心的特性。
封装是我们将客观事物抽象成对象模型,在看完上文提到的什么是面向对象,我觉得这个毋庸置疑了。如果没有这种封装,我们所依赖的基本单元--对象也就不存在。
继承是我们在封装一类事物,而这类事物有着明显的派生特点,但又不完全相同时,可以考虑一层一层将它们继承下来。有人用IS-A来表述这种派生关系。如果使用得当,继承可以减少我们所定义的这类对象时,重复的代码。但要注意的是,如果我们强行将关联不大的事物进行继承,会破坏代码的可扩展性,因为继承本身是一种将多种对象耦合在一起的做法。所以实践中,组合的做法是优于继承的,只有真正联系非常紧密,且具有这种派生关系的对象才适合使用继承来进行设计。
多态的含义是一组标准,有多种实现。标准的出现,很好地发挥了隔离不同类别功能的作用,而多态则使得我们的软件具有更好的扩展性。
【真正理解面向对象】
从开始接触Java这门语言开始,我就知道了面向对象这个概念。但是经过多年的编程,我也不能说自已已经真正学到了面向对象的精髓。
从一开始对于各种概念的完全不理解,到现在已经能有些体会,在实际开发过程中逐渐有意识地使用这套方法来指导自己进行开发工作,我觉得真正困难的地方在于,所有的概念都是看得懂的,但是没有实际的编程体验,没有真正面临需求的频繁变更、紧急上线、蹩脚的设计导致功能完全做不下去等等实际的困难之前,这些东西就算我理解了字面含义,也会很快就忘掉。这套编程方法,是前人基于实际问题而总结的经验,我觉得最行之有效的办法就是:了解面向对象的含义,看明白这些设计模式的含义,然后忘了它,接下来在实际的开发过程中不断让那些不灵活,不方便的设计给自己带来体验,最终再次相遇,你会发现这些模式确实是良药。如果没有经历过那些不良体验,也就无法对它们的便捷有所体会,必须要实践再实践才能偶有所得,纸上谈兵则毫无益处。
【没有银弹】
说到底,面向对象是一种软件编程方法,在解决问题的过程中,它也会带来问题。
如果你看过Spring的源码,一定曾被这里面复杂的概念所困惑,当我们一层一层去查看这些代码时,可能已经忘了这个模块究竟是想干嘛了。只有真正理解了它的意图,并站在面向对象设计原则这个角度,我们才能说它的设计很优雅,但是这种设计带来的代码复杂度也是很高的。所以,我们不能教条地认为,面向对象就一定是好的,我们做任何设计时,都要考虑到面向对象的原则。
真正的开发过程中,如果我们的目标是将复杂的需求化繁为简,那么我们可以考虑使用面向对象;如果只是为了快速实现一个简单的需求,或者追求性能,或者是一个很固定地小模块,那就大胆的使用面向过程。
时刻记得面向对象只是一种工具,是为我们解决问题提供便利的,不要为了那些原则让我们的开发过程陷入过度设计的陷阱中。适合我们系统的设计才是真正优雅的设计。