7.1 面向对象编程思想

7.1.1 一切皆是映射

《易传·系辞上传》:“易有太极,是生两仪,两仪生四象,四象生八卦。” 如今的互联网世界,其基石却是01(阴阳),不得不佩服我华夏先祖的博大精深的智慧。

一切皆是映射

计算机领域中的所有问题,都可以通过向上一层进行抽象封装来解决.这里的封装的本质概念,其实就是“映射”。

就好比通过的电子电路中的电平进行01逻辑映射,于是有了布尔代数,数字逻辑电路系统;

对01逻辑的进一步封装抽象成CPU指令集映射,诞生了汇编语言;

通过汇编语言的向上抽象一层编译解释器,于是有了pascal,fortran,C语言;

再对核心函数api进行封装形成开发包(Development Kit), 于是有了Java,C++ 。

从面向过程到面向对象,再到设计模式,架构设计,面向服务,Sass/Pass/Iass等等的思想,各种软件理论思想五花八门,但万变不离其宗。

  • 你要解决一个怎样的问题?
  • 你的问题领域是怎样的?
  • 你的模型(数据结构)是什么?
  • 你的算法是什么?
  • 你对这个世界的本质认知是怎样的?
  • 你的业务领域的逻辑问题,流程是什么?

Grady Booch:我对OO编程的目标从来就不是复用。相反,对我来说,对象提供了一种处理复杂性的方式。这个问题可以追溯到亚里士多德:您把这个世界视为过程还是对象?在OO兴起运动之前,编程以过程为中心—例如结构化设计方法。然而,系统已经到达了超越其处理能力的复杂性极点。有了对象,我们能够通过提升抽象级别来构建更大的、更复杂的系统—我认为,这才是面向对象编程运动的真正胜利。

最初, 人们使用物理的或逻辑的二进制机器指令来编写程序, 尝试着表达思想中的逻辑, 控制硬件计算和显示, 发现是可行的;

接着, 创造了助记符 —— 汇编语言, 比机器指令更容易记忆;

再接着, 创造了编译器、解释器和计算机高级语言, 能够以人类友好自然的方式去编写程序, 在牺牲少量性能的情况下, 获得比汇编语言更强且更容易使用的语句控制能力:条件、分支、循环, 以及更多的语言特性: 指针、结构体、联合体、枚举等, 还创造了函数, 能够将一系列指令封装成一个独立的逻辑块反复使用;

逐渐地,产生了面向过程的编程方法;

后来, 人们发现将数据和逻辑封装成对象, 更接近于现实世界, 且更容易维护大型软件, 又出现了面向对象的编程语言和编程方法学, 增加了新的语言特性: 继承、 多态、 模板、 异常错误。

为了不必重复开发常见工具和任务, 人们创造和封装了容器及算法、SDK, 垃圾回收器, 甚至是并发库;

为了让计算机语言更有力更有效率地表达各种现实逻辑, 消解软件开发中遇到的冲突, 还在语言中支持了元编程、 高阶函数, 闭包 等有用特性。

为了更高效率地开发可靠的软件和应用程序, 人们逐渐构建了代码编辑器、 IDE、 代码版本管理工具、公共库、应用框架、 可复用组件、系统规范、网络协议、 语言标准等, 针对遇到的问题提出了许多不同的思路和解决方案, 并总结提炼成特定的技术和设计模式, 还探讨和形成了不少软件开发过程, 用来保证最终发布的软件质量。 尽管编写的这些软件和工具还存在不少 BUG ,但是它们都“奇迹般地存活”, 并共同构建了今天蔚为壮观的互联网时代的电商,互联网金融,云计算,大数据,物联网,机器智能等等的“虚拟世界”。

7.1.2 二进制01与易经阴阳

二进制数是用0和1两个数码来表示的数。它的基数为2,进位规则是“逢二进一”,借位规则是“借一当二”,由18世纪德国数理哲学大师莱布尼兹发现。当前的计算机系统使用的基本上是二进制系统。

19世纪爱尔兰逻辑学家B对逻辑命题的思考过程转化为对符号0,1的某种代数演算,二进制是逢2进位的进位制。0、1是基本算符。因为它只使用0、1两个数字符号,非常简单方便,易于用电子方式实现。

二进制的发现直接导致了电子计算器和计算机的发明,并让计算机得到了迅速的普及,进入各行各业,成为人类生活和生产的重要工具。

二进制的实质是通过两个数字“0”和“1”来描述事件。在人类的生产、生活等许多领域,我们可以通过计算机来虚拟地描述现实中存在的事件,并能通过给定的条件和参数模拟事件变化的规律。二进制的计算机几乎是万能的,能将我们生活的现实世界完美复制,并且还能根据我们人类给定的条件模拟在现实世界难以实现的各种实验。

但是,不论计算机能给我们如何多变、如何完美、如何复杂的画面,其本源只是简单的“0”和“1”。“0”和“1”在计算机中通过不同的组合与再组合,模拟出一个纷繁复杂、包罗万象的虚拟世界。我们简单图示如下:

Kotlin极简教程

二进制的“0”和“1”通过计算机里能够创造出一个虚拟的、纷繁的世界。自然界中的阴阳形成了现实世界的万事万物。

所以自然世界的“阴”“阳”作为基础切实地造就了复杂的现实世界,计算机的“0”和“1”形象地模拟现实世界的一切现象,易学中的“卦”和“阴阳爻”抽象地揭示了自然界存在的事件和其变化规律。

所以说,编程的本质跟大自然创造万物的本质是一样的。

7.1.3 从面向过程到面向对象

从IBM公司的约翰·巴库斯在1957年开发出世界上第一个高级程序设计语言Fortran至今,高级程序设计语言的发展已经经历了整整半个世纪。在这期间,程序设计语言主要经历了从面向过程(如C和Pascal语言)到面向对象(如C++和Java语言),再到面向组件编程(如.NET平台下的C#语言),以及面向服务架构技术(如SOA、Service以及最近很火的微服务架构)等。

面向过程编程

结构化编程思想的核心:功能分解(自顶向下,逐层细化)。

1971年4月份的 Communications of ACM上,尼古拉斯·沃斯(Niklaus Wirth,1934年2月15日—, 结构化编程思想的创始人。因发明了Euler、Alogo-W、Modula和Pascal等一系列优秀的编程语言并提出了结构化编程思想而在1984年获得了图灵奖。)发表了论文“通过逐步求精方式开发程序’(Program Development by Stepwise Refinement),首次提出“结构化程序设计”(structure programming)的概念。

不要求一步就编制成可执行的程序,而是分若干步进行,逐步求精。

第一步编出的程序抽象度最高,第二步编出的程序抽象度有所降低…… 最后一步编出的程序即为可执行的程序。

用这种方法编程,似乎复杂,实际上优点很多,可使程序易读、易写、易调试、易维护、易保证其正确性及验证其正确性。

结构化程序设计方法又称为“自顶向下”或“逐步求精”法,在程序设计领域引发了一场革命,成为程序开发的一个标准方法,尤其是在后来发展起来的软件工程中获得广泛应用。有人评价说Wirth的结构化程序设计概念“完全改变了人们对程序设计的思维方式”,这是一点也不夸张的。

尼古拉斯· 沃思教授在编程界提出了一个著名的公式:

程序 = 数据结构 + 算法

面向对象编程

面向对象编程思想的核心:应对变化,提高复用。

阿伦·凯(Alan Kay):面向对象编程思想的创始人。2003年因在面向对象编程上所做的巨大贡献而获得图灵奖。

The best way to predict the future is to invent it,预测未来最好的方法是创造它!(Alan Kay)

阿伦·凯是Smalltalk面向对象编程语言的发明人之一,也是面向对象编程思想的创始人之一,同时,他还是笔记本电脑最早的构想者和现代Windows GUI的建筑师。最早提出PC概念和互联网的也是阿伦·凯,所以人们都尊称他为“预言大师”。他是当今IT界屈指可数的技术天才级人物。

面向对象编程思想主要是复用性和灵活性(弹性)。复用性是面向对象编程的一个主要机制。灵活性主要是应对变化的特性,因为客户的需求是不断改变的,怎样适应客户需求的变化,这是软件设计灵活性或者说是弹性的问题。

Java是一种面向对象编程语言,它基于Smalltalk语言,作为OOP语言,它具有以下五个基本特性:

  1. 万物皆对象,每一个对象都会存储数据,并且可以对自身执行操作。因此,每一个对象包含两部分:成员变量和成员方法。在成员方法中可以改变成员变量的值。
  2. 程序是对象的集合,他们通过发送消息来告知彼此所要做的事情,也就是调用相应的成员函数。
  3. 每一个对象都有自己的由其他对象所构成的存储,也就是说在创建新对象的时候可以在成员变量中使用已存在的对象。
  4. 每个对象都拥有其类型,每个对象都是某个类的一个实例,每一个类区别于其它类的特性就是可以向它发送什么类型的消息,也就是它定义了哪些成员函数。
  5. 某一个特定类型的所有对象都可以接受同样的消息。另一种对对象的描述为:对象具有状态(数据,成员变量)、行为(操作,成员方法)和标识(成员名,内存地址)。

面向对象语言其实是对现实生活中的实物的抽象。

每个对象能够接受的请求(消息)由对象的接口所定义,而在程序中必须由满足这些请求的代码,这段代码称之为这个接口的实现。当向某个对象发送消息(请求)时,这个对象便知道该消息的目的(该方法的实现已定义),然后执行相应的代码。

我们经常说一些代码片段是优雅的或美观的,实际上意味着它们更容易被人类有限的思维所处理。

对于程序的复合而言,好的代码是它的表面积要比体积增长的慢。

代码块的“表面积”是是我们复合代码块时所需要的信息(接口API协议定义)。代码块的“体积”就是接口内部的实现逻辑(API背后的实现代码)。

在面向对象编程中,一个理想的对象应该是只暴露它的抽象接口(纯表面, 无体积),其方法则扮演箭头的角色。如果为了理解一个对象如何与其他对象进行复合,当你发现不得不深入挖掘对象的实现之时,此时你所用的编程范式的原本优势就荡然无存了。

面向组件和面向服务

  • 面向组件

我们知道面向对象支持重用,但是重用的单元很小,一般是类;而面向组件则不同,它可以重用多个类甚至一个程序。也就是说面向组件支持更大范围内的重用,开发效率更高。如果把面向对象比作重用零件,那么面向组件则是重用部件。

  • 面向服务

将系统进行功能化,每个功能提供一种服务。现在非常流行微服务MicroService技术以及SOA(面向服务架构)技术。

面向过程(Procedure)→面向对象(Object)→ 面向组件(Component) →面向服务(Service)

正如解决数学问题通常我们会谈“思想”,诸如反证法、化繁为简等,解决计算机问题也有很多非常出色的思想。思想之所以称为思想,是因为“思想”有拓展性与引导性,可以解决一系列问题。

解决问题的复杂程度直接取决于抽象的种类及质量。过将结构、性质不同的底层实现进行封装,向上提供统一的API接口,让使用者觉得就是在使用一个统一的资源,或者让使用者觉得自己在使用一个本来底层不直接提供、“虚拟”出来的资源。

计算机中的所有问题 , 都可以通过向上抽象封装一层来解决。同样的,任何复杂的问题, 最终总能够回归最本质,最简单。

面向对象编程是一种自顶向下的程序设计方法。万事万物都是对象,对象有其行为(方法),状态(成员变量,属性)。OOP是一种编程思想,而不是针对某个语言而言的。当然,语言影响思维方式,思维依赖语言的表达,这也是辩证的来看。

所谓“面向对象语言”,其实经典的“过程式语言”(比如Pascal,C),也能体现面向对象的思想。所谓“类”和“对象”,就是C语言里面的抽象数据类型结构体(struct)。

而面向对象的多态是唯一相比struct多付出的代价,也是最重要的特性。这就是SmallTalk、Java这样的面向对象语言所提供的特性。

回到一个古老的话题:程序是什么?

在面向对象的编程世界里,下面的这个公式

程序 = 算法 + 数据结构

可以简单重构成:

程序 = 基于对象操作的算法 + 以对象为最小单位的数据结构

封装总是为了减少操作粒度,数据结构上的封装导致了数据的减少,自然减少了问题求解的复杂度;对代码的封装使得代码得以复用,减少了代码的体积,同样使问题简化。这个时候,算法操作的就是一个抽象概念的集合。

在面向对象的程序设计中,我们便少不了集合类容器。容器就用来存放一类有共同抽象概念的东西。这里说有共同概念的东西(而没有说对象),其实,就是我们上一个章节中讲到的泛型。这样对于一个通用的算法,我们就可以最大化的实现复用,作用于的集合。

面向对象的本质就是让对象有多态性,把不同对象以同一特性来归组,统一处理。至于所谓继承、虚表、等等概念,只是其实现的细节。

在遵循这些面向对象设计原则基础上,前辈们总结出一些解决不同问题场景的设计模式,以GOF的23中设计模式最为知名。

我们用一幅图简单概括一下面向对象编程的知识框架:

Kotlin极简教程

讲了这么多思考性的思想层面的东西,我们下面来开始Kotlin的面向对象编程的学习。Kotlin对面向对象编程是完全支持的。