手记

图解编程:到底什么是“抽象”和“面向对象”

本系列Github地址:https://github.com/Jigsawk/CatsayJava

我是一只来自乡下的小猫,因为出生时发出了“啾啾”的声音,加上铲屎官实在是太懒了,所以他就给我取名“啾啾”。

这是我来上海打工的第一年,听大家说现在互联网行业比较好,工资比价高,所以我也加入了学习编程的队伍。不过我好像比较笨,很多问题都要复习多遍才能理解,所以我把他们都记录在这里。

这是我在2020年的第一篇笔记,大家多多指教呀。

在学习编程的开始,我就遇到了一个问题。我看很多人都在说“要对问题进行抽象”、“结合面向对象的编程思想”,那到底什么是“抽象”,什么是“面向对象”?好在我的铲屎官寒食君就是一名程序员,所以我就“不耻下问”了。

1. 什么是抽象?

这是一个好问题,程序员们都在说抽象,但是新人一头雾水,到底什么才是抽象?按照我个人的理解,当你遇到一个问题,你能将其中的关键信息提取出来并进行建模,就是“抽象”。

还是不好理解?比如“汇编语言是对底层机器的轻微抽象”,因为机器底层都是二进制,对于人来说,使用二进制来编程实在太反人类了,于是就有人对底层机器指令进行封装,发明了汇编语言,相比纯二进制,它的可读性强多了。但是它依然太难了,于是就渐渐出现了命令式语言,接着出现了高级语言,直到现在大部分程序员通过写英文就能够来命令机器如何运行。

2. 如何进行抽象?

“所有的编程语言都提供抽象机制。可以认为,人们能够解决的问题的复杂性直接取决于抽象的类型和质量。”

换句话说,写代码之前,先好好思考,不要抄起键盘猛如虎,一看bug二百五。磨刀不误砍柴功,这就是设计阶段。比如上面提到的汇编语言,它依然是基于计算机的结构来设计的,当你使用它时,你就要强行将自己的脑回路变成计算机的电路来思考。这是比较困难的,这也是为什么现在很多人都觉得写汇编语言的都是大佬。但是缺点是,这样的程序不仅难写,而且维护起来也很难。

所以今天我们要说的主题就是一种更符合人类思维的抽象方式:面向对象。

虽然随着时代的发展,面向对象的一些缺陷显露出来,也遭到了一些人的抨击,但我个人认为这依然无法阻碍其成为最伟大的编程思想之一,它依然是强有力的生产方式。相较于某些只能解决特定类型问题的抽象方式,面向对象是通用的,它将所有事物都视为Object,这和现实世界一一对应,所以一每一个现实世界中的问题都能映射到程序中。

3. 那到底什么是面向对象?

SmallTalk是第一门成功的面向对象语言,Java是最成功的一门面向对象语言。Alan Kay总结了这五条面向对象的法则,我们一起来理解一下。

  • 万物皆为对象

你可以将现实生活中的任何事物都看作是(抽象为)一个对象(Object),对象的特点是:拥有自身的属性行为能力。

  • 程序是对象的集合,他们通过发送消息来告知彼此所要做的

对象和对象之间不是完全独立的,他们可以通过互相发送“消息”来沟通。对象既然拥有行为能力,那么他们可以为其他对象做一些事,前面指的“发消息”,事实上就是指对对象的方法的调用。

  • 每个对象都有自己的由其他对象所构成的存储。

每个对象都可以包含其他对象,比如“汽车对象”可以包含“轮子对象”。这样就能够通过包含关系来构建复杂的体系,比如你需要构建“火箭对象”,那必然是各个系统对象的组合,各个系统对象又是由更小的模块对象组成的。

  • 每个对象都拥有其类型

这个很好理解,万事万物都有其类型,比如我们人类就是一个“类型”。编程语言中通常用class来定义类型。

  • 某一特定类型的所有对象都可以接收同样的消息。

这句话可能有点难理解,什么是“某一特定类型的所有对象”?这就是“多态”,举个最通俗的例子,啾啾你是“美短”类型的猫,奶泡是“布偶”类型的猫,美短类型和布偶类型都属于猫类型,所以你们都属于猫类型,你们都拥有“猫类型”的属性和行为能力,听懂了吗?关于”多态“,下文会详细讲到。

总结来说,对象具有属性和方法。

4. 为什么要有面向对象?

因为程序员可以通过定义类来更快、更容易地抽象问题,程序员可以自由地创建新的数据类型来对问题进行建模,而不用只能通过计算机现有的、机器中基本存储单元来解决问题。

依赖其他编程思想也能够构建仿真程序,但是面向对象能够更容易更高效地降解问题。但是面向对象不是想象的那么简单,如何对现实问题与程序建立合理优雅的映射依然是设计的难点。

这里就不得不引出面向对象的三大特性:封装继承多态

**5. 三大特性:**封装、继承、多态

这三个词很多人可能都已经烂熟于心了,如果面试被问到,已经能够达到条件反射,脱口而出的地步。但假如面试官让你深入聊聊,还能够保持自信吗?如果开始眼神躲闪,语无伦次,那基本凉凉,因为这是比较基本的问题,很能够看出一个人对基础的掌握程度,所以当啾啾你要学编程时,我首先要把这些告诉你听。

  • 封装

关于封装,上面也已经提到了,当我们创建一个“类”我们就需要定义它的属性和方法。属性代表它“有什么”,方法代表它“能做什么”。将它们“封装”到一起,就成为了一个类,按照这个类,我们可以构建对象。

此外,类的属性和方法也是拥有权限等级的,分为public、private、protected,具体三者代表什么意思,这里不赘述了,不了解的小伙伴可以去查些资料。

因此,由类构建的对象只向其他对象暴露开放的属性和方法。这是有好处的,假如你设计了一个类或一组程序构件开放给他人使用,这就能够进行访问控制,第一,他们不能随心所欲地篡改,或者由于疏忽修改你的代码,他们只需要关心那些对他们开放的内容;第二,当你进行了内部修改,只要不改变对外接口,对其他调用方则是无感知的,他们不用修改相应旧代码,保证了稳定和效率。

更重要的,也是很多人忽视的。一个类如果封装得好,那么它将拥有很高的“内聚性”,什么是内聚性?高内聚代表它只完成自己该做的事,不涉足和自己无关的事,作为代码构件,很容易被重用,能有和其他构件很容易地组合,以发挥更大的能力,高内聚常常意味着代码质量。

  • 继承

在讲完封装之后,你应该对“类”、“对象”有了更深的认识。

试想这样一种情况:假如已经有了一个类“猫”拥有了属性:四只爪子、两只耳朵、一条尾巴等多个属性,以及“喵喵叫”这样的方法。现在我想要创建一个“美短猫”的类型,那我还得重新新建一个类,再写一遍“四只耳朵”、“两只眼睛”… 这显然太麻烦了。所以就出现了“继承”,即“美短猫”类型继承“猫”类型,获取父类型的全部属性和方法。此外,子类型可以新增属性,比如“灰白条纹”;以及新增方法,比如“擅长跑酷”,重写当前类中继承的方法。这时候,继承所描述的是**“is-a”的关系**,参照上例:美短猫 is a 猫。

通过继承,既能体现两个相关对象的差异性,又能体现出相似性。清晰的类继承结构是非常重要的,一旦你的项目变得越来越大,不合理的继承关系会让你的代码臃肿并且难于拓展。

理想化的继承是纯粹的,但是,并非所有的设计都是完美的。有时候,你不得不在子类中添加一些方法,而这些方法可能是非常独特,不够一般化的。举个很简单的例子,“多啦A梦”从外观和设定上来说,也应该继承“猫类型”,有拥有超能力,所以此时我们认为“多啦A梦” is like a “猫”,哆啦A梦像一只猫,这就是**“is like a”**关系。

  • 多态

看到这里,是不是感觉有点渐渐复杂了起来?坚持一下,下面将进入面向对象最美妙的东西:多态。

多态,这个名字听上去就很高大上啊,顾名思义就是多种状态,这里的状态指的就是”对象类型“。什么意思?有时候,我们在进行编程时,并不关心当前操作的具体是什么类型的对象。

就像宠物店的员工在喂猫时,只关心自己喂的是猫,而不关心具体是什么猫。只有当真正喂食的时候,才根据具体猫的类型来喂不同的猫粮。这样,一旦宠物店引进了新品种的猫,该员工也不需要修改动作,只知道自己喂的是猫,具体怎么喂,到时候看猫的类型再说。如果将该员工视作是程序,那么无论宠物店引进什么品种,该程序都能完美兼容,不需要修改。

这就是“多态”,程序操作的是一个泛化的基类,在编译期,编译器只能确保被调用方法的存在,以及类型检查,但是不知道该方法究竟会调用哪个具体方法,执行哪段具体代码,直到运行时,才能根据对象的具体类型来作出判断,执行相应的代码,这就是“后期绑定”的概念。

如果你还是有点不太明白,我们再来温习下上面的例子。假如喂猫员工是个程序,程序员不必写这样的代码“如果遇到美短,喂这种猫粮;如果遇到布偶,喂这种猫粮;如果遇到机器猫,喂铜锣烧…”这样写一定不是一个好程序员,不仅非常繁琐,而且种类是穷举不完的。程序员只要做的是告诉这个程序“你要知道喂的是猫,具体喂什么,等这只猫来了再决定吧”,这就给编程带来了极大的灵活性。

这么一说,是不是瞬间明白了?多态就是这么简单,但也是这么奇妙。

这是我准备在2020年持续更新的一个系列

致力于用一种新方式讲解编程

难度会逐渐上升

希望得到你的关注转发

我和啾啾感激不尽

0人推荐
随时随地看视频
慕课网APP