面向对象的思考过程(原书第5版)
上QQ阅读APP看书,第一时间看更新

1.3 过程式编程与面向对象编程对比

在我们深入了解面向对象开发的优势之前,先考虑一个更基本的问题:究竟什么是对象?这既是一个复杂的问题,也是一个简单的问题。复杂是因为学习任何一种软件开发方法论都非易事,简单是因为人们已经在按对象的方式进行思考。

Tip

在面向对象大师Robert Martin的一个YouTube演讲视频中,他提到“人们按照对象的方式思考”这种说法是市场销售人员杜撰的。这一点引人深思。

例如,当你看到一个人,你会把他看作一个对象。一个对象由两部分组成:属性及行为。人具有属性,比如眼睛颜色、年龄、身高等。人也具有行为,比如行走、说话、呼吸等。对象的基本定义是一个包含了数据和行为的实体。前面的“和”是面向对象编程与其他编程方法论的核心区别。例如,在过程式编程中,代码放置在完全不同的函数或程序中。在如图1.1所示的理想情况下,程序即为“黑盒”(black box),接收输入,然后输出。数据放置在单独的结构中,通过函数或程序进行操作。

图1.1 黑盒

面向对象与过程式编程的不同之处

在面向对象设计中,属性及行为包含在单个对象中,而在过程式或结构式设计中,属性和行为通常是分开的。

虽然面向对象设计越来越流行,但有个现实问题最初阻碍了面向对象设计的推广,那就是很多非面向对象的系统还在正常工作。因此,没有任何商业动力来将其改造为面向对象的系统。任何熟悉计算机系统的人都知道,不管对系统的修改有多小,都极可能引发灾难性的后果。

接受面向对象的数据库也存在相同的问题。随着面向对象开发方式的发展,貌似面向对象的数据库将替代关系型数据库。然而,这绝不会发生。关系型数据库得到了大量的商业投资,而现有的关系型数据库可以正常工作,这一重要因素阻碍了向面向对象的数据库的转换。将关系型数据库转换为面向对象的数据库花销巨大又充满风险,因此没有一个足够令人信服的理由来支持转换。

事实上,企业已经找到了一个折中方案。当今很多软件开发实践糅合了几种开发方法论,比如面向对象和面向过程方法论。

如图1.2所示,结构化编程中数据往往与程序分离,而且数据是全局的,所以在代码作用域之外依然可以很容易地修改数据。这意味着对数据的访问是失控的,并且不可预测(因为很多函数都可以访问全局数据)。而且,由于你无法控制谁能访问数据,因此测试和调试将变得更加困难。对象通过将数据和行为组合到一个完整的包中解决了这些问题。

图1.2 使用全局数据

恰当的设计

如果设计是恰当的,那么在面向对象模型中则不会有诸如全局数据的元素。这使得面向对象系统具有很高的数据完整性。

对象并不会完全替代其他的软件开发范式,它是一种进化的响应。结构化的程序有复杂的数据结构,比如数组等。C++有结构体(structure),结构体具有对象(类)的很多特性。

然而,对象比数据结构体以及原始数据类型(比如整型和字符串)更丰富。对象包含整型和字符串之类的实体,用于表示属性,对象也包含方法,用于表示行为。在对象中,方法用于操作数据及其他行为。更重要的是,你可以控制对对象中成员(包括属性及方法)的访问。这意味着某些成员(属性和方法)可以对其他对象隐藏起来。例如,名为Math的对象包含两个整数,叫作myInt1和myInt2。同时,Math对象也包括了必要的方法来存取myInt1和myInt2的值。它也包括一个叫作sum()的方法对这两个整数求和。

数据隐藏

在面向对象的术语中,数据表现为属性,行为表现为方法。限制访问具体属性和(或)方法的行为叫作数据隐藏。

将属性和方法合并到同一个实体中,在面向对象中这种方式叫作封装(encapsulation)。我们可以控制对Math对象的数据的访问。比如将这些整数定义为禁止外部访问,即除了Math对象之外的其他函数无法操作整数myInt1和myInt2。

合理的类设计指导

请注意,完全有可能创建一个设计差劲的面向对象的类,该类没有对类属性的访问进行任何限制。重要的是,使用面向对象设计可以像使用其他编程方法一样有效地设计糟糕的代码。只要坚持合理的类设计指导即可(详见第5章)。

另一个名为myObject的对象如何获取myInt1和myInt2的总和?它会询问Math对象,即myObject会给Math对象发送一个消息。图1.3展示了这两个对象如何通过方法进行通信。该消息其实就是调用Math对象的sum方法,sum方法把值返回给myObject。巧妙之处在于myObject无须知道总和是如何计算出来的。使用这种设计方法,你可以修改Math对象计算总和的方式而无须对myObject做任何修改(这意味着获取总和的方式无须改变)。你需要的是总和,而不用关心它是如何计算出来的。

我们可以通过一个简单的计算器示例来演示该概念。当使用计算器求和时,你只需使用计算器提供的接口,即键盘和LED显示器。计算器有一个求和方法,当你按下正确的按键序列就会调用它。你会得到正确的返回结果,然而你无须知道结果是如何计算出来的,你既不用关心电路控制,也不用关心算法。

图1.3 对象之间的通信

求和不是myObject的责任,它是Math对象的责任。由于myObject可以访问Math对象,他可以发送相应的信息并获取正确的结果。通常,对象不应当操作其他对象的内部数据(即myObject不应当直接修改myInt1和myInt2的值)。而且随着持续开发,最好多构建一些具有明确任务的小对象,而不应该构建包含很多方法的大对象。