面向对象编程之我见

我将之前的一篇文章翻译成了中文,你能在这些地方读到英文原文:

我已经写了三年多面向对象(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."

「面向对象对我而言,仅仅代表了消息传递,状态的保存、保护和隐藏,还有对一切操作的延迟绑定。」

由此可见,面向对象编程有三个基本特征:

  1. 消息传递

    有的观点认为「类」是面向对象的基础。但是如果我们仔细想一想,一个「类」只是一个数据结构加上和这个数据结构有关的行为。而这里的「行为」,也正是 Alan Kay 所说的「消息」。

  2. 状态的保存、保护和隐藏

    这个过程也就是我们常说的「封装」。「封装」原则要求我们尽可能地隐藏我们的数据结构。当我们使用一个封装良好的对象时,我们不需要知道它保存了什么数据,只需要了解这个对象能够接收哪些「消息」。

  3. 对一切操作的延迟绑定

    延迟绑定,又称为动态绑定,是面向对象语言实现的终极目标之一。「消息传递」和「封装」的出现都是为了实现这一目标。

    同一个「消息」能够发给不同的对象,也能在不同的对象之间不断传递,因此,「消息」和代码之间的绑定可以被尽可能地延迟。

    有了合适的「封装」,我们就不必考虑绑定的过程、细节。

    除了这两个基本特征之外,面向对象语言的其他特性(继承、代理、组合、多态)都可以说是为了「延迟绑定」这一目标而实现的。

    有了「延迟绑定」,你就能只通过发消息来让对象做它应该做的事。你不需要考虑这个对象是怎么应对这个「消息」的。对这个「消息」的处理能够发生在任何地方,这个对象本身、它的父类、另外的对象,等等。这大大简化了我们在读代码时的负担。因为你只用理解同一抽象层级上的逻辑,而不用关心当前层级以下更加具体的实现。理解整个代码库都变得更加容易。

    如果没有「延迟绑定」,那我们就需要理解这个程序里的所有的逻辑。我们就会像一个需要事事操心的经理,而不是一个带领团队、放眼全局的 CEO。

怎么去设计一个好的面向对象程序?

面向对象不是一个新的概念。它已经存在了将近半个世纪了(第一个面向对象语言 Simula 诞生在 1967 年)。

所以,我们已经有了很多可以参考的资料和研究:

  1. 了解面向对象设计的原则

    首先,我们需要知道一些面向对象设计原则。否则,即使一个设计良好的程序摆在我们面前,我们也认不出来。在这些已有的原则总结里,我认为 SOLID 原则是最有效,但也最难理解的原则。我还没有在实际项目中见到一个能够很好地符合 SOLID 原则的。

    以下是一些链接以供参考:

  2. 不断重构、优化

    重构是在不改变代码逻辑行为的前提下,重新组织、改善代码结构的实践。有两个有效的原则能帮我们更高效地重构代码:

    1. 在加新功能之前重构

      之前的我经常通过加 if 语句来快速地增加新功能。但是随着时间的推移,函数们就逐渐庞大起来,整个项目也很难继续维护。

      但如果我们在每次加新功能之前,都重构一下,让加新功能变得更简单,我们的代码也就能更好地保持简洁。

    2. 在功能完成之后重构

      经过一些实践后我还认识到:想要第一次就写出一个设计良好的程序,几乎是不可能的。

      只有当我们完成了一个新功能,我们才能对这个项目的领域有一个更深刻的认识。有了这份认识,我们才能更好地组织我们的代码。

      当然,这样的重构并不仅限于功能上,在完成了一个函数,一个类,一个测试之后,我们都应该回头审视一下刚写下的代码,看看有没有可以改进的空间。

  3. 善用 UML 图等辅助工具

    当我在大学里第一次学到 UML 时,我还没意识到这个设计工具的强大。我以为,他们只是简单的图表而已,学习它们只是浪费时间。但后来,我发现我错了。

    这些工具在我们设计、重构我们的系统的时候,都是非常有用的。他们是比代码、文档更加经济、有效的沟通设计的方式。

    因此,当我们需要沟通我们的设计时,学会 UML 就变得非常重要。

    团队内部需要一份标准来减少沟通上的误会。UML 就提供了这样一份标准。

学习面向对象最重要的两个关注点

  1. 拥抱变化

    「变化」是软件工程中最大的难题。

    如果一个程序在完成之后就不需要变化了,那我们就没有什么好担心的。只需要能让它实现我们一开始想要的功能,怎么写我们的代码都行。

    但是,这样的程序在现实世界里太少了。我们总会需要给程序增加新的功能,修复旧的缺陷。

    在这个前提下,我们只能把「变化」一词时刻放在心上。拥抱变化,并为变化做好准备。

  2. 关注代码的可读性

    我对《计算机程序的构造和解释》中的这句话印象深刻:

    "Programs must be written for people to read, and only incidentally for machines to execute."

    「程序是写给人来阅读的,只是碰巧会交给机器执行。」

    一个设计优秀的程序能够让别人轻松地读懂,并且进行修改、除错。

    换句话说,写简单的代码,而不是聪明的代码。

谨以此文,与大家共勉。