面向对象编程之我见
我将之前的一篇文章翻译成了中文,你能在这些地方读到英文原文:
- The Secret to Object-Oriented Programming – Ekohe – Medium
- My Summary for Object-Oriented Programming
我已经写了三年多面向对象(Object-Oriented Programming)的程序了(包括 C++, Java, Python, Ruby 等面向对象语言),但直到最近我才觉得自己理解了一些面向对象的真谛。
在一个 Ruby 项目中工作了几个月后,我渐渐感受到了面向过程编程(Procedural Programming)的痛点。因此,我翻阅了一些关于面向对象的书籍:代码整洁之道, 重构,Practical Object-Oriented Design in Ruby,Objects on Rails,等等。终于从中获得了一些灵感。这些书帮我更好地理解了什么是面向对象设计,以及如何实现这样的设计。
这篇文章就是我现在对面向对象编程理解的一个小结。
面向对象的基础
Alan Kay(Smalltalk 以及面向对象的作者)对面向对象编程的解释非常的精辟:
"OOP to me means only messaging, local retention, and protection and hiding of state-process, and extreme late-binding of all things."
「面向对象对我而言,仅仅代表了消息传递,状态的保存、保护和隐藏,还有对一切操作的延迟绑定。」
由此可见,面向对象编程有三个基本特征:
消息传递
有的观点认为「类」是面向对象的基础。但是如果我们仔细想一想,一个「类」只是一个数据结构加上和这个数据结构有关的行为。而这里的「行为」,也正是 Alan Kay 所说的「消息」。
状态的保存、保护和隐藏
这个过程也就是我们常说的「封装」。「封装」原则要求我们尽可能地隐藏我们的数据结构。当我们使用一个封装良好的对象时,我们不需要知道它保存了什么数据,只需要了解这个对象能够接收哪些「消息」。
对一切操作的延迟绑定
延迟绑定,又称为动态绑定,是面向对象语言实现的终极目标之一。「消息传递」和「封装」的出现都是为了实现这一目标。
同一个「消息」能够发给不同的对象,也能在不同的对象之间不断传递,因此,「消息」和代码之间的绑定可以被尽可能地延迟。
有了合适的「封装」,我们就不必考虑绑定的过程、细节。
除了这两个基本特征之外,面向对象语言的其他特性(继承、代理、组合、多态)都可以说是为了「延迟绑定」这一目标而实现的。
有了「延迟绑定」,你就能只通过发消息来让对象做它应该做的事。你不需要考虑这个对象是怎么应对这个「消息」的。对这个「消息」的处理能够发生在任何地方,这个对象本身、它的父类、另外的对象,等等。这大大简化了我们在读代码时的负担。因为你只用理解同一抽象层级上的逻辑,而不用关心当前层级以下更加具体的实现。理解整个代码库都变得更加容易。
如果没有「延迟绑定」,那我们就需要理解这个程序里的所有的逻辑。我们就会像一个需要事事操心的经理,而不是一个带领团队、放眼全局的 CEO。
怎么去设计一个好的面向对象程序?
面向对象不是一个新的概念。它已经存在了将近半个世纪了(第一个面向对象语言 Simula 诞生在 1967 年)。
所以,我们已经有了很多可以参考的资料和研究:
了解面向对象设计的原则
首先,我们需要知道一些面向对象设计原则。否则,即使一个设计良好的程序摆在我们面前,我们也认不出来。在这些已有的原则总结里,我认为 SOLID 原则是最有效,但也最难理解的原则。我还没有在实际项目中见到一个能够很好地符合 SOLID 原则的。
以下是一些链接以供参考:
不断重构、优化
重构是在不改变代码逻辑行为的前提下,重新组织、改善代码结构的实践。有两个有效的原则能帮我们更高效地重构代码:
在加新功能之前重构
之前的我经常通过加
if
语句来快速地增加新功能。但是随着时间的推移,函数们就逐渐庞大起来,整个项目也很难继续维护。但如果我们在每次加新功能之前,都重构一下,让加新功能变得更简单,我们的代码也就能更好地保持简洁。
在功能完成之后重构
经过一些实践后我还认识到:想要第一次就写出一个设计良好的程序,几乎是不可能的。
只有当我们完成了一个新功能,我们才能对这个项目的领域有一个更深刻的认识。有了这份认识,我们才能更好地组织我们的代码。
当然,这样的重构并不仅限于功能上,在完成了一个函数,一个类,一个测试之后,我们都应该回头审视一下刚写下的代码,看看有没有可以改进的空间。
善用 UML 图等辅助工具
当我在大学里第一次学到 UML 时,我还没意识到这个设计工具的强大。我以为,他们只是简单的图表而已,学习它们只是浪费时间。但后来,我发现我错了。
这些工具在我们设计、重构我们的系统的时候,都是非常有用的。他们是比代码、文档更加经济、有效的沟通设计的方式。
因此,当我们需要沟通我们的设计时,学会 UML 就变得非常重要。
团队内部需要一份标准来减少沟通上的误会。UML 就提供了这样一份标准。
学习面向对象最重要的两个关注点
拥抱变化
「变化」是软件工程中最大的难题。
如果一个程序在完成之后就不需要变化了,那我们就没有什么好担心的。只需要能让它实现我们一开始想要的功能,怎么写我们的代码都行。
但是,这样的程序在现实世界里太少了。我们总会需要给程序增加新的功能,修复旧的缺陷。
在这个前提下,我们只能把「变化」一词时刻放在心上。拥抱变化,并为变化做好准备。
关注代码的可读性
我对《计算机程序的构造和解释》中的这句话印象深刻:
"Programs must be written for people to read, and only incidentally for machines to execute."
「程序是写给人来阅读的,只是碰巧会交给机器执行。」
一个设计优秀的程序能够让别人轻松地读懂,并且进行修改、除错。
换句话说,写简单的代码,而不是聪明的代码。
谨以此文,与大家共勉。