- 「MoreThanJava」 宣扬的是 「学习,不止 CODE」,本系列 Java 基础教程是自己在结合各方面的知识之后,对 Java 基础的一个总回顾,旨在 「帮助新朋友快速高质量的学习」。
- 当然 不论新老朋友 我相信您都可以 从中获益。如果觉得 「不错」 的朋友,欢迎 「关注 + 留言 + 分享」,文末有完整的获取链接,您的支持是我前进的最大的动力!
Part 1. 面向对象设计概述
面向对象程序设计 (Object-Oriented Programming, OOP) 是当今主流的程序设计范型,它取代了 20
世纪 70
年代的 “结构化” 或过程式编程技术。由于 Java 是面向对象的,所以必须熟悉 OOP 才能够很好地使用 Java。
了解抽象
抽象的作用是将复杂的机制隐藏在一个对象中,仅保留我们与之交互所必须的信息。
为了说明这一点,我们可以想象平时使用 「电梯」 的场景。
如果您在办公楼工作,这可能是您日常工作的一部分。你按下向上或向下按钮,然后等待门滑开。完成操作后,您进入一个 “盒子”,该 “盒子” 的一面墙上有一个按钮面板,然后按下所需的按钮。当电梯到达您要去到的楼层后,您会挤过其他人然后走出去。
要使用电梯,您只需要了解如何按下正确的按钮就可以达到目的。
而隐藏在电梯背后的支持它工作的一系列东西 —— 滑轮系统、机械、电线、减震器、安全系统等等… 您可以完全不知道也完全不必操心…
电梯这个 “铁盒子” 以及相应的按钮面板,就是对整个「运输系统」成功的抽象 (事实上电梯背后还包含检修、维护等一系列事情…),它隐藏了足够多的细节,也极大地方便了我们的生活。
什么是对象
简单来说,对象是对现实世界的抽象。 (例如上方对整个运输系统抽象之后,就得到了「电梯」这个对象…)
什么东西是对象?什么东西不是对象?这是一个困扰哲学家数千年的问题。勒内·笛卡尔 (17世纪的哲学家) 观察到,人类是用面向对象的方式看待世界的 (例如与电梯的交互)。人类的大脑会从对象的角度认识世界 (例如鸟类、鱼类),我们的思想和记忆也被组织成物体和它们之间的关系 (例如,鸟吃虫)。
对象像是一种模板
亚里士多德大概是第一个深入研究 类型 (type) 的哲学家,它曾经提出过 鱼类 和 鸟类 这样的概念。所有的对象都是唯一的,但同时也是具有相同的特性和行为的对象所归属的类的一部分。
这就好像我们拿着一个模具,我们可以使用该模具制作出各种各样东西,每个东西都有自己的 “个性”,但它们又都遵循一些相同的基本模式:
对象的特征
我们可以把你的「银行账户」抽象成一个对象,但它不是由物质构成的。(虽然您和银行可以使用纸张和其他材料来记录您的账户,但您的账户独立于这些材料而存在。)
虽然它不是物质的,但你的账户是有 属性 的 (余额、利率、持有者等…),你可以对它做一些事情 (存款、取款、查看余额等…,),它自己也可以做一些事情 (交易收费、积累利息等…)。
这足够清楚吧。事实上,这些特征它们都有名字:
- 对象具有 标识 identity;(每个对象都是独立的个体)
- 对象具有 状态 state;(它具有各种可能会改变的属性)
- 对象具有 行为 behavior;(它可以做事情,也可以让别人对它做事情)
这就是对一个物体的一般描述。(上面的列表来自于 1994
年 Grady Booch
/Addison-Wesley
出版的《面向对象分析与设计》一书。) 当你开始编写面向对象的软件时,你会发现这个列表将帮助你决定你的对象应该是什么样。
编程语言中的抽象过程
所有编程语言都提供抽象机制。可以认为,人们所能够解决的问题的复杂性直接取决于抽象的类型和质量。
所谓的 “类型” 是指 “所抽象的是什么?”。
汇编语言是对底层机器语言的轻微抽象,接着出现的许多 “命令式” 语言 (如 FORTRAN、BASIC、C 等…) 都是对汇编语言的进一步抽象。
这些语言在汇编语言基础上有了很大幅度的改进,但是它们所作的主要抽象仍要求在解决问题时要基于计算机的结构,而不是基于所要解决的问题的结构来考虑。
传统的结构化程序设计通过设计一系列的过程 (即算法) 来求解问题。一旦确定了这些过程,就要开始考虑存储数据的适当方式。
这就是
Pascal
语言的设计者Niklaus Wirth
将其著作命名为《算法 + 数据结构 = 程序》(Algorithms + Data Structures = Programs, Prentice Hall,1975
) 的原因。需要注意的是,在
Wirth
的这个书名中,算法是第一位的,数据结构是第二位的,这就明确的表述了程序员的工作方式。首先要确定如何操作数据,然后再决定如何组织数据的结构,以便于操作数据。而 OOP 却调换了这个次序,将数据放在第一位,然后再考虑操作数据的算法。(在 OOP 中,也有说法是:程序 = 对象 + 交互)
这使得程序员必须建立起在 机器模型 (位于 “解空间” 内,这是你对问题建模的地方,例如计算机) 和 实际需要解决问题的模型 (位于 “问题空间” 内,这是问题存在的地方,例如一项业务) 之间的 关联。
建立这种映射是费力的,而且这不属于编程语言固有的功能,这使得程序难以编写,并且维护代价高昂,同时也产生了作为副产物的整个 “编程方法” 行业。
面向对象思想的突破
另一种对机器建模的方式就是针对待解问题建模。
早期的编程语言,例如 LISP
和 APL
,都是选择一些特定的视角来 “解释世界” (分别敌营 “所有问题最终都是列表” 或者 “所有问题都是算法形式的”)。PROLOG
则将所有问题都转换成决策链。此外还产生了基于约束条件编程的语言和专门通过对图形符号操作来实现编程的语言 (后来被证明限制性过强)。
这些方式对于它们本身所要解决的 特定类型的问题 都是不错的解决方案,但是一旦 超出 其特定领域,它们就力不从心了。
面向对象的方式通过向程序员提供表示问题空间中的元素的工具而更近了一步。
这种表示方式非常通用,使得程序员不会受限于任何特定类型的问题。我们把问题空间中的一些基本元素进一步抽象成解空间中的 “对象”。这种思想的实质是:程序可以通过添加新类型的对象使其自身适用于某个特定的问题。
因此,当你在阅读描述解决方案的代码的同时,也是在阅读问题的表述。相比之前的语言,这是一种更灵活和更强力的语言抽象。所以,OOP 允许根据问题来描述问题,而不是根据运行解决方案的计算机来描述问题。
面向对象软件的最重要的突破之一就是允许我们按照 自然的面向对象的大脑思维方式相匹配的方式组织软件。我们希望使用具有属性并能够与其他对象进行交互的对象,而不是直接使用更改主存储器中的 bit 数据的机器指令。当然,在机器层面上什么也没有改变——bit 数据仍是由机器指令操作的,但至少我们不用再考虑机器指令了!
对于一些规模较小的问题,将其分解为过程的开发方式比较理想。面向对象更加适合解决规模较大的问题。要想实现一个简单的 Web 浏览器可能需要大约
2000
个过程,这些过程可能需要对一组全局数据进行操作。
采用面向对象的设计风格,可能只需要大约
100
个类,每个类平均包含20
个方法。这明显易于程序员掌握,也容易找到 BUG。(假设给定对象的数据出错了,在访问这个数据项的20
个方法中查找错误要比在2000
个过程中查找要容易多了)
OOP 的起源
正如我们上面描述的那样,面向对象的编程是当今不可回避的。让我们来看看它是如何变成现实的。
时间回到上世纪 60
年代,那个时候计算机图形还不存在。当时,美国计算机科学家 Ivan Edward Sutherland
实现了能够绘图的应用程序,名叫:SketchPad。
它是专门为设计人员开发的,它允许设计人员使用手写笔通过计算机绘制简单的几何形状,例如三角形、正方形、圆形等。该项目也是 计算机辅助设计 CAD 的起点。
这成为了面向对象编程的 奠基典范 之一。
因为在 Ivan 的程序设计中,使用了我们现在称为 “对象” 的表现形式来描述现实生活中的几何图形,这些图形对于设计人员来说是完全可以理解的!
这其中没有无穷无尽的变量和函数,而是通过具体的几何图形 (对象形式) 来描述 (包括上下文数据,都存储在变量中) 和操作 (函数实现) 进行分组,并以一种关系进行管理这些特定的元素。
这些东西在现在都有确切的名称。(分别对应 “属性” 和 “方法”)
OOP 的规范化
Ivan 的项目和其他一些项目在 1967
年影响了 Simula 编程语言。该语言第一次直接将面向对象的思想引入到了 编程语言中 (重大更新之后被称为 Simula-67
)。
1970
年代,Xerox (负责鼠标和图形界面的发明) 在个人电脑上工作。他们希望通过操纵 GUI 和鼠标来创建任何人都可以轻松使用的计算机。
为了表示屏幕上的所有元素并支持其显示和操作的逻辑,由艾伦·凯 (Alan Kay) 领导的团队创建了 SmallTalk 语言,该语言的灵感来自 Simula。根据许多资料显示,这标志着我们今天使用的面向对象编程概念的正式确立!
OOP 的普及化
上述这些方法在 1981
年开始流行,并成为了伟大的面向对象语言的起点,例如:
- Objective-C 是 iOS 本机开发的原始语言。从那以后,Apple 对其进行了改进和增强,它仍然是 iOS 开发人员的常见选择。
- C ++ 是 C 编程语言的面向对象版本。C 和 C++ 仍被广泛使用,尤其是在非常专业的行业中。
如我们所见,在编程方面取得了令人难以置信的进步,这是对以下问题的解决方案:简化软件开发!
面向对象设计的特殊效率从何而来?
- 部分影响来自于更清晰的表达复杂系统的方式;
- 也许最重要的原因 (也是从操作系统体系结构派生而来的) 是,当您给某人一个结构时,您很少希望他们拥有无限的特权。仅仅进行类型匹配甚至还不能满足需求。保护某些对象而不保护某些对象也不是非常合理有用。
正确执行封装不仅是对状态抽象的承诺,而且是消除编程中面向状态的隐喻的一种承诺。
Part 2. 类与对象概述
- 图片来源:https://javatutorial.net/java-oop
简单的说,类是对象的蓝图或模板,而对象是类的实例。
这个解释虽然有点像用概念在解释概念,但是从这句话我们至少可以看出,类是抽象的概念,而对象是具体的东西。
在面向对象编程的世界中,一切皆为对象,对象都有属性和行为,每个对象都是独一无二的,而且对象一定属于某个类 (型)。当我们把一大堆拥有共同特征的对象的静态特征 (属性) 和动态特征 (行为) 都抽取出来后,就可以定义出一个叫做 “类” 的东西。
定义类
使用类几乎可以模拟任何东西。假设我们要编写一个表示小狗 Dog 的简单类 —— 它表示的不是特定的小狗,而是任何小狗。
对于大多数宠物狗,我们都知道些什么呢?—— 它们都有名字和年龄,还会叫、会吃东西。由于大多数的小狗都具备上述两项信息 (名字和年龄) 和两种行为 (叫和吃东西),所以我们的 Dog 类将包含它们,这个类看上去会是这样:
代码实现起来大概会像这样: