从编程发展史谈面向对象
当软件还非常简单的时候,我们只需要面向过程编程:
定义函数 函数一 函数二 函数三 函数四 定义数据 数据一 数据二 数据三 数据四 最后 各种函数,数据的操作。
当软件发展起来后,我们的软件变得越来越大,代码量越来越多,复杂度远超Hello World的时候,我们的编写就有麻烦了:函数和数据会定义得非常多,这时候就面临两个问题。
首先是命名冲突。英文单词也就那么几个,可能写着写着取名时就没合适的短词用了,为了避免冲突,只能把函数名取得越来越长。
然后是代码重复。比如你做一个计算器程序,你的函数就要确保处理的是合理的数据,这样最起码加减乘除四个函数里,你就都要写对参数进行检测的代码,写四遍或者复制粘贴四遍不会很烦,但多了你就痛苦了,而且因为这些检测代码是跟你的加减乘除函数的本意是无关的,却一定要写在那里,使代码变得不好阅读,意图模糊。
比如你做一个计算器程序,你的函数就要确保处理的是合理的数据,这样最起码加减乘除四个函数里,你就都要写对参数进行检测的代码,写四遍或者复制粘贴四遍不会很烦,但多了你就痛苦了,而且因为这些检测代码是跟你的加减乘除函数的本意是无关的,却一定要写在那里,使代码变得不好阅读,意图模糊。
随着软件业的发展,解决办法就要出来了:
代码重复。我们可以用函数里面调用函数的方法,比如把检测代码抽出来成一个独立函数,然后加减乘除四个函数运行时只要调用一下检测函数对参数进行检查就可以了。分布在四个函数里的重复代码变成了一个函数,是不是好维护多了。
命名冲突,我们就把这一堆函数们进行分类吧。
//分类之前,我们取名只能这样取: 检测 整数加 整数减 整数乘 整数除 复数加 复数减 复数乘 复数除 小数加 ... //进行分类后我们取名可以这样取: 整数 { 检测 加 减 乘 除 } 复数 { 检测 加 减 乘 除 } 小数 { 检测 加 减 乘 除 } 分数 { 检测 加 减 乘 除 }
这个时候一种叫做类的概念就呼之欲出了,这样我们打开一个整数类代码文件,里面就是简简单单的加减乘除四个函数,简单清晰不会跟外面的其他加减乘除函数命名冲突。
当然,进行归类后,又出现了各种问题和与之对应的解决方法,比如四个类中的检测也是应该提取出来的,所以简单的起因最终发展出什么继承衍生之类挺复杂的一套编程模式。然后学术界那帮人就又乱起什么高大上的名字了,所谓面向对象程序设计去祸害大学里那帮孩子。
就算未来出来一个什么新的面向XX编程,我们也不用多想为什么会出现,肯定是为了解决麻烦而已。
//上面进行归类以后,代码其实还是不好维护的,然后我们就继续提取为: 数 { 检测 加 减 乘 除 } 整数 { 沿用上面数的设计 } 小数 { 沿用上面数的设计 }
所谓继承,就是数这个类的整体设计,沿用给整数,分数小数这些类,作为他们的编写大纲去编写加减乘除这些函数的具体代码。根据整数,分数,小数各自的性质,做出各自的调整。
这时数这个类,如果你给它里面的加减乘除函数的写了一些很粗糙简单的代码,就叫做父类,基础类。子类们“继承”了父类(把代码进行了复杂化)。
如果没写,那这个类其实就只是个设计图,叫做抽象类。子类们“实现”了抽象类(把空空的设计变成了具体代码)。
模版是什么?像 C++ 这种复杂成狗的语言是强类型的,就是给变量进行了类型区分的,比如整数类型,双整数类型。很明显这两种变量所能容纳的数据体积是不一样的,单个函数不能通吃多种类型的参数,我们就可能会面临下面两套代码并存的局面。
//第一套代码: 单整数类 { 单整数加 单整数减 单整数乘 单整数除 } //第二套代码 双整数类 { 双整数加 双整数减 双整数乘 双整数除 }
所以 C++ 跟其他强类型语言为我们提供了一个所谓模版功能:
<变量类型>整数 { <变量类型>加 <变量类型>减 <变量类型>乘 <变量类型>除 } /* 整数类等于把变量类型设置为整数,套上模版 双整数类等于把变量类型设置为双整数,套上模版 这样就写了一份代码,得到了两份类的代码。 当然,弱类型的编程语言,比如 JavaScript 或者 PHP 是没有这种烦恼的,因为变量没有类型之分。但变量类型有时候还是很重要的,弱类型语言里就会出现类似数加字符串这种运算,可能并不是程序员的预期和本意,所以比起强类型性语言而言经常会出现很多无聊的BUG。 */
上面发展出了父类之后,我们发现编程还是有问题的,小数类:
小数类 { 加 减 乘 除 }
如果我们需要一个能自动实现结果四舍五入的小数计算类,同时又需要一个不实现结果四舍五入的小数计算类,怎么办呢,难道要写两个类吗?不要。所以做出了“实例”或者“对象”这一东西,首先把类改成:
小数类 { 标识变量:是否四舍五入 标识变量:是否限定小数点后位数 构造函数(设置上面的标识) 加(会根据上面两个标识变量输出不同结果) 减(会根据上面两个标识变量输出不同结果) 乘(会根据上面两个标识变量输出不同结果) 除(会根据上面两个标识变量输出不同结果) }
这样,我们就写一个类,但是通过构造函数,把一份代码,构造出了行为稍微有点不同的两个实例供我们使用,这时候名词来了,不能进行实例化微调化的类,叫做静态类,函数们的行为是固定的。不能实例化的类,其实只是函数们的一个集合归纳,只是对函数进行了整理,功能的强大和编码的自由灵活度是不够的。
能够进行实例化,变化出各种行为各自不大一样的实例的类,我们一般就把它们叫做类了,因为最常见。
程序员们也就能保持代码简单的同时而又可以很方便进行代码行为微调了。
面向过程与面向对象的区别,由如何把大象装进冰箱来看
1.面向过程
为了把大象装进冰箱,需要3个过程。
/* 思路: 1.把冰箱门打开(得到打开门的冰箱) 2.把大象装进去(打开门后,得到里面装着大象的冰箱) 3.把冰箱门关上(打开门、装好大象后,获得关好门的冰箱) */ //每个过程有一个阶段性的目标,依次完成这些过程,就能把大象装进冰箱。 //写法一: 1:冰箱开门(冰箱) 2:冰箱装进(冰箱,大象) 3:冰箱关门(冰箱) //写法一变体: 1:(冰箱开门 冰箱) 2:(冰箱装进 冰箱大象) 3:(冰箱关门 冰箱) //写法二: 冰箱关门(冰箱装进(冰箱开门(冰箱),大象)) //写法二变体: (冰箱关门 (冰箱装进 (冰箱开门 冰箱) 大象))
面向对象
为了把大象装进冰箱,需要做三个动作(或者叫行为)。每个动作有一个执行者,它就是对象。
/* 思路: 1.冰箱,你给我把门打开 2.冰箱,你给我把大象装进去(或者说,大象,你给我钻到冰箱里去) 3.冰箱,你给我把门关上 */ //依次完成这些动作,你就可以把大象装进去 //写法一: 1:冰箱.开门() 2:冰箱.装进(大象) 3:冰箱.关门() //写法二: 冰箱.开门().装进(大象).关门()
总结
再来一个简单的栗子:
有一天你去买电脑,发现什么显卡啊、内存啊、处理器啊、价格一概不懂,于是你就懵逼了,没办法啊不懂自己就去网上查呗,于是自己捣鼓了半天终于弄懂了这才放心去买–这是面向过程。
有一天你去买电脑,自己还是不懂,于是你就叫上了一个懂电脑的哥们,其余所有事情都交给他来做了,你只管付钱了。–这是面向对象。
**面向过程:执行者;
面向对象:指挥者;**
面向过程还是对象,名词并不重要,重要的是哪个适合,软件工程师们自己根据项目复杂度进行选择罢了。