一:什么是编程范式?
编程是程序员用特定的语法 + 数据结构 + 算法组成的代码来告诉计算机如何执行任务的过程。
如果把编程的过程比喻为练习武功,那么编程范式指的就是武林中的各种流派,而在编程的世界里最常见的两大流派便是:面向过程与面向对象。
“功夫的流派没有高低之分,只有习武的人才有高低之分“,在编程世界里更是这样,面向过程与面向对象在不同的场景下都各有优劣,谁好谁坏不能一概而论。
一个程序是程序员为了得到一个任务结果而编写的一组指令的集合,正所谓条条大路通罗马,实现一个任务的方式有很多种不同的方法,对这些不同的编程方式的特点进行归纳总结得出来的编程方式类别,即为编程范式,不同的编程范式本质上代表对各种各类的任务采取的不同的解决问题的思路,大多数语言只支持一种编程范式,当然也有一些语言可以同时支持多种编程范式。两种最重要的编程范式分别是面向过程编程和面向对象编程。
二:什么是面向过程编程?
面向过程编程依赖 - procedures,一个procedure包含一组要被进行计算的步骤, 面向过程又被称为top-down languages, 就是程序从上到下一步步执行,一步步从上到下,从头到尾的解决问题 。基本设计思路就是程序一开始是要着手解决一个大的问题,然后把一个大问题分解成很多个小问题或子过程,这些子过程再执行的过程再继续分解直到小问题足够简单到可以在一个小步骤范围内解决。
举个典型的面向过程的例子, 写一个数据远程备份程序, 分三步,本地数据打包,上传至云服务器,测试备份文件可用性。
def cloud_upload(file): print("\nconnecting cloud storage center...") print("cloud storage connected.") print("upload file...xxx..to cloud...", file) print(‘close connection.....‘) def data_backup(folder): print("找到要备份的目录...", folder) print("将备份文件打包,移至相应目录...") return ‘/tmp/backup20181103.zip‘ def data_backup_test(): print("\n从另外一台机器将备份文件从远程cloud center下载,看文件是否无损") def main(): zip_file = data_backup("c:\\users\\alex\欧美100G高清无码") cloud_upload(zip_file) data_backup_test() if __name__ == ‘__main__‘: main()
这个变量,那这个子过程你也要修改,假如又有一个其它子程序依赖这个子过程 , 那就会发生一连串的影响,随着程序越来越大, 这种编程方式的维护难度会越来越高。
test = 1 def cloud_upload(file): if test == 1: print("\nconnecting cloud storage center...") print("cloud storage connected.") print("upload file...xxx..to cloud...", file) print(‘close connection.....‘) return True else: print("不备份") return False def data_backup(folder): print("找到要备份的目录...", folder) print("将备份文件打包,移至相应目录...") return ‘/tmp/backup20181103.zip‘ def data_backup_test(upload_res): if upload_res == 1: print("\n从另外一台机器将备份文件从远程cloud center下载,看文件是否无损") else: print("upload error,不备份") def main(): zip_file = data_backup("c:\\users\\alex\欧美100G高清无码") res = cloud_upload(zip_file) data_backup_test(res) if __name__ == ‘__main__‘: main()
所以我们一般认为, 如果你只是写一些简单的脚本,去做一些一次性任务,用面向过程的方式是极好的,但如果你要处理的任务是复杂的,且需要不断迭代和维护 的, 那还是用面向对象最方便了。
三:面向对象编程的引子
你现在是一家游戏公司的开发人员,现在需要你开发一款叫做<人狗大战>的游戏,你就思考呀,人狗作战,那至少需要2个角色,一个是人, 一个是狗,且人和狗都有不同的技能,比如人拿棍打狗, 狗可以咬人,怎么描述这种不同的角色和他们的功能呢?
你搜罗了自己掌握的所有技能,写出了下面的代码来描述这两个角色
person = { ‘name‘:‘Alex‘, ‘attack‘: 100, #杀伤力 ‘life_value‘:1000 } dog = { ‘name‘:‘Peiqi‘, ‘attack‘: 200, #杀伤力 ‘life_value‘:800 }
一个字典表示一个角色实体,但是如果有多条狗和多个人一起打呢?那就得写多个字典
person = { ‘name‘:‘Alex‘, ‘attack‘: 100, #杀伤力 ‘life_value‘:1000 } person2 = { ‘name‘:‘Black Girl‘, ‘attack‘: 100, #杀伤力 ‘life_value‘:600 } dog = { ‘name‘:‘Peiqi‘, ‘attack‘: 200, #杀伤力 ‘life_value‘:800 }
这样是有问题的,因为如果你字典里的值不小心定义错了,把attack写成了了atteck的话,那整个程序就有问题了。so 你很快想出了改进方案,把字典放进函数
def person(name,attack,life_value): data = { ‘name‘:name, ‘attack‘:attack, ‘life_value‘:life_value, } return data def dog(name, attack, life_value): data = { ‘name‘: name, ‘attack‘: attack, ‘life_value‘: life_value, } return data alex = person("Alex",100,1000) rain = person("Black girl",80,700) d = dog("PeiQi",200,800)
好,现在角色定义好了,还差每个角色的功能,人打狗,狗咬人的功能要定义出来
def attack(p,d): """人打狗功能""" d[‘life_value‘] -= p[‘attack‘] #被打了,要掉血 print("人[%s] 打了 狗[%s]。。。,[%s]的生命值还有[%s]" % (p[‘name‘], d[‘name‘],d[‘name‘],d[‘life_value‘])) def bite(d,p): """狗咬人功能""" p[‘life_value‘] -= d[‘attack‘] print("狗[%s] 咬了 人[%s]。。。,[%s]的生命值还有[%s]" % (d[‘name‘], p[‘name‘],p[‘name‘],p[‘life_value‘])) alex = person("Alex",100,1000) black_girl = person("Black girl",80,700) d = dog("PeiQi",200,800) attack(alex,d) bite(d,black_girl)
但是要设定许多许多的功能,我们是不是要一点点的加?那么久特别麻烦,改这个改那个的,为了解决上面的问题,我们使用面向对象。
四:什么是面向对象?
OOP(Object Oriented Programing)编程是利用“类”和“对象”来创建各种模型来实现对真实世界的描述。
怎么说呢?
核心是“对象”二字,要理解对象为何物,必须把自己当成上帝,在上帝眼里,世间存在的万物皆为对象,不存在的也可以创造出来。程序员基于面向对象设计程序就好比如来设计西游记,如来要解决的问题是把经书传给东土大唐,如来并没有考虑问题的解决流程,而是设计出了负责取经的师傅四人:唐僧,沙和尚,猪八戒,孙悟空,负责骚扰的一群妖魔鬼怪,以及负责保驾护航的一众神仙,这些全都是对象,然后取经开始,就是师徒四人与妖魔鬼怪神仙交互着直到完成取经任务。所以说基于面向对象设计程序就好比在创造一个世界,世界是由一个个对象组成,而你就是这个世界的上帝。
我们从西游记中的任何一个人物对象都不难总结出:对象是特征与技能的结合体。比如孙悟空的特征是:毛脸雷公嘴,技能是:七十二变、火眼金睛等。
与面向过程机械式的思维方式形成鲜明对比,面向对象更加注重对现实世界而非流程的模拟,是一种“上帝式”的思维方式。
面向过程 == 个人视角
我要去做大保健,我只需考虑,我有没有钱,去哪家店,怎么去,做什么价位的就可以, 你的每一步都要通过程序定义出来,写死了,在这个程序里,你只被设定了去做大保健的功能, 你说中途我想去个ktv,那可能会导致整个程序的逻辑都得更改。 用面向过程的方式写代码, 那你care的就是整个事情的执行过程
面向对象 == 上帝视角
如果你是上帝,你现在要创世纪,把这么多人、动物、山河造出来,上帝光靠自己干, 一个一个的造人,多累呀,让你干这个活,你肯定是先造模子,一个男人模子, 一个女人模子,剩下的就一个个复制就行啦。这个模子的作用是什么? 模子定义了人这个物种所具备的所有特征\(或者说,我们把具备这些特征的个体归为人类\)。 这个世界上所有的东西都是你定义的,你需要用最高效的方式去造世界, 最高效的方式就是,先把世界按物种、样貌、有无生命等各种维度分类, 然后给每类东西建模型,再让其在不脱离你基本横型定义的框架下, 自我繁衍(世界要多姿多彩,所以即使是同一物种,也要有些不一样)
五:面向对象和面向过程的优缺点对比
面向过程
优点:
- 复杂的问题流程化,进而简单化(一个复杂的问题,分成一个个小的步骤去实现,实现小的步骤将会非常简单)
- 举个典型的面向过程的例子, 写一个数据远程备份程序, 分三步,本地数据打包,上传至云服务器,测试备份文件可用性。
缺点:
- 一套流水线或者流程就是用来解决一个问题,比如生产汽水的流水线无法生产汽车,即便是能,也得是大改,改一个组件,与其相关的组件都需要修改,牵一发而动全身,扩展性极差。
- 比如我们修改了步骤二的函数cloud_upload的逻辑,那么依赖于步骤二结果才能正常执行的步骤三的函数data_backup_test相关的逻辑也需要修改,这就造成了连锁反应,而这一弊端会随着程序的增大而变得越发的糟糕,我们程序的维护难度将会越来越大。
应用场景:
面向过程的程序设计思想一般用于那些功能一旦实现之后就很少需要改变的场景, 如果你只是写一些简单的脚本,去做一些一次性任务,用面向过程的方式是极好的,著名的例子有Linux內核,git,以及Apache HTTP Server等。但如果你要处理的任务是复杂的,且需要不断迭代和维护 的, 那还是用面向对象最方便了。
面向对象
优点:
解决了面向过程可扩展性低的问题,需要强调的是,对于一个软件质量来说,面向对象的程序设计并不代表全部,面向对象的程序设计只是用来解决扩展性问题。
缺点:
编程的复杂度远高于面向过程,不了解面向对象而立即上手并基于它设计程序,极容易出现过度设计的问题,而且在一些扩展性要求低的场景使用面向对象会徒增编程难度,比如管理linux系统的shell脚本程序就不适合用面向对象去设计,面向过程反而更加适合。
应用场景:
当然是应用于需求经常变化的软件中,一般需求的变化都集中在用户层,互联网应用,企业内部软件,游戏等都是面向对象的程序设计大显身手的好地方。
六:为什么要用面向对象?
- 使程序更加容易扩展和易更改,使开发效率变的更高
- 基于面向对象的程序可以使它人更加容易理解你的代码逻辑,从而使团队开发变得更从容。
七:面向对象名词解释
类:一个类即是对一类拥有相同属性的对象的抽象、蓝图、原型、模板。在类中定义了这些对象的都具备的属性(variables(data))、共同的方法
属性:人类包含很多特征,把这些特征用程序来描述的话,叫做属性,比如年龄、身高、性别、姓名等都叫做属性,一个类中,可以有多个属性
方法:人类不止有身高、年龄、性别这些属性,还能做好多事情,比如说话、走路、吃饭等,相比较于属性是名词,说话、走路是动词,这些动词用程序来描述就叫做方法。
实例(对象):一个对象即是一个类的实例化后实例,一个类必须经过实例化后方可在程序中调用,一个类可以实例化多个对象,每个对象亦可以有不同的属性,就像人类是指所有人,每个人是指具体的对象,人与人之前有共性,亦有不同
实例化:把一个类转变为一个对象的过程就叫实例化
八:面向对象三大特性
1,Encapsulation 封装
在类中对数据的赋值、内部调用对外部用户是透明的,这使类变成了一个胶囊或容器,里面包含着类的数据和方法
2,Inheritance 继承
一个类可以派生出子类,在这个父类里定义的属性、方法自动被子类继承
3,Polymorphism 多态
多态是面向对象的重要特性,简单点说:“一个接口,多种实现”,指一个基类中派生出了不同的子类,且每个子类在继承了同样的方法名的同时又对父类的方法做了不同的实现,这就是同一种事物表现出的多种形态。
编程其实就是一个将具体世界进行抽象化的过程,多态就是抽象化的一种体现,把一系列具体事物的共同点抽象出来, 再通过这个抽象的事物, 与不同的具体事物进行对话。
对不同类的对象发出相同的消息将会有不同的行为。比如,你的老板让所有员工在九点钟开始工作, 他只要在九点钟的时候说:“开始工作”即可,而不需要对销售人员说:“开始销售工作”,对技术人员说:“开始技术工作”, 因为“员工”是一个抽象的事物, 只要是员工就可以开始工作,他知道这一点就行了。至于每个员工,当然会各司其职,做各自的工作。
多态允许将子类的对象当作父类的对象使用,某父类型的引用指向其子类型的对象,调用的方法是该子类型的方法。这里引用和调用方法的代码编译前就已经决定了,而引用所指向的对象可以在运行期间动态绑定
九:Python中关于OPP的常用术语
9.1 抽象/实现
抽象指对现实世界问题和实体的本质表现,行为和特征建模,建立一个相关的子集,可以用于 绘程序结构,从而实现这种模型。抽象不仅包括这种模型的数据属性,还定义了这些数据的接口。
对某种抽象的实现就是对此数据及与之相关接口的现实化(realization)。现实化这个过程对于客户 程序应当是透明而且无关的。
9.2 封装/接口
封装描述了对数据/信息进行隐藏的观念,它对数据属性提供接口和访问函数。通过任何客户端直接对数据的访问,无视接口,与封装性都是背道而驰的,除非程序员允许这些操作。作为实现的 一部分,客户端根本就不需要知道在封装之后,数据属性是如何组织的。在Python中,所有的类属性都是公开的,但名字可能被“混淆”了,以阻止未经授权的访问,但仅此而已,再没有其他预防措施了。这就需要在设计时,对数据提供相应的接口,以免客户程序通过不规范的操作来存取封装的数据属性。
注意:封装绝不是等于“把不想让别人看到、以后可能修改的东西用private隐藏起来”
真正的封装是,经过深入的思考,做出良好的抽象,给出“完整且最小”的接口,并使得内部细节可以对外透明
(注意:对外透明的意思是,外部调用者可以顺利的得到自己想要的任何功能,完全意识不到内部细节的存在)
9.3 合成
合成扩充了对类的 述,使得多个不同的类合成为一个大的类,来解决现实问题。合成 述了 一个异常复杂的系统,比如一个类由其它类组成,更小的组件也可能是其它的类,数据属性及行为, 所有这些合在一起,彼此是“有一个”的关系。
9.4 派生/继承/继承结构
派生描述了子类衍生出新的特性,新类保留已存类类型中所有需要的数据和行为,但允许修改或者其它的自定义操作,都不会修改原类的定义。
继承描述了子类属性从祖先类继承这样一种方式
继承结构表示多“代”派生,可以述成一个“族谱”,连续的子类,与祖先类都有关系。
9.5 泛化/特化
基于继承
泛化表示所有子类与其父类及祖先类有一样的特点。
特化描述所有子类的自定义,也就是,什么属性让它与其祖先类不同。
9.6 多态与多态性
多态指的是同一种事物的多种状态:水这种事物有多种不同的状态:冰,水蒸气
多态性的概念指出了对象如何通过他们共同的属性和动作来操作及访问,而不需考虑他们具体的类。
冰,水蒸气,都继承于水,它们都有一个同名的方法就是变成云,但是冰.变云(),与水蒸气.变云()是截然不同的过程,虽然调用的方法都一样
9.7 自省/反射
自省也称作反射,这个性质展示了某对象是如何在运行期取得自身信息的。如果传一个对象给你,你可以查出它有什么能力,这是一项强大的特性。如果Python不支持某种形式的自省功能,dir和type内建函数,将很难正常工作。还有那些特殊属性,像__dict__,__name__及__doc__
十:小白容易犯的错误
1.面向对象的程序设计看起来高大上,所以我在编程时就应该保证通篇class,这样写出的程序一定是好的程序(面向对象只适合那些可扩展性要求比较高的场景)
2.很多人喜欢说面向对象三大特性(这是从哪传出来的,封装,多态,继承?漏洞太多太多,好吧暂且称为三大特性),那么我在基于面向对象编程时,我一定要让我定义的类中完整的包含这三种特性,这样写肯定是好的程序
好家伙,我说降龙十八掌有十八掌,那么你每次跟人干仗都要从第一掌打到第18掌这才显得你会了是么:面对敌人,你打到第三掌对方就已经倒下了,你说,不行,你给老子起来,老子还没有show完...
3.类有类属性,实例有实例属性,所以我们在定义class时一定要定义出那么几个类属性,想不到怎么办,那就使劲的想,定义的越多越牛逼
这就犯了一个严重的错误,程序越早面向对象,死的越早,为啥面向对象,因为我们要将数据与功能结合到一起,程序整体的结构都没有出来,或者说需要考虑的问题你都没有搞清楚个八九不离十,你就开始面向对象了,这就导致了,你在那里干想,自以为想通了,定义了一堆属性,结果后来又都用不到,或者想不通到底应该定义啥,那就一直想吧,想着想着就疯了。
你见过哪家公司要开发一个软件,上来就开始写,肯定是频繁的开会讨论计划。
4.既然这么麻烦,那么我彻底解脱了,我们不要用面向对象编程了,你啊,你有大才,你能成事啊,傻叉。
十一:面向对象的软件开发
很多人在学完了python的class机制之后,遇到一个生产中的问题,还是会懵逼,这其实太正常了,因为任何程序的开发都是先设计后编程,python的class机制只不过是一种编程方式,如果你硬要拿着class去和你的问题死磕,变得更加懵逼都是分分钟的事,在以前,软件的开发相对简单,从任务的分析到编写程序,再到程序的调试,可以由一个人或一个小组去完成。但是随着软件规模的迅速增大,软件任意面临的问题十分复杂,需要考虑的因素太多,在一个软件中所产生的错误和隐藏的错误、未知的错误可能达到惊人的程度,这也不是在设计阶段就完全解决的。
所以软件的开发其实一整套规范,我们所学的只是其中的一小部分,一个完整的开发过程,需要明确每个阶段的任务,在保证一个阶段正确的前提下再进行下一个阶段的工作,称之为软件工程
面向对象的软件工程包括下面几个部:
1.面向对象分析(object oriented analysis ,OOA)
软件工程中的系统分析阶段,要求分析员和用户结合在一起,对用户的需求做出精确的分析和明确的表述,从大的方面解析软件系统应该做什么,而不是怎么去做。面向对象的分析要按照面向对象的概念和方法,在对任务的分析中,从客观存在的事物和事物之间的关系,贵南出有关的对象(对象的‘特征’和‘技能’)以及对象之间的联系,并将具有相同属性和行为的对象用一个类class来标识。
建立一个能反映这是工作情况的需求模型,此时的模型是粗略的。
2 面向对象设计(object oriented design,OOD)
根据面向对象分析阶段形成的需求模型,对每一部分分别进行具体的设计。
首先是类的设计,类的设计可能包含多个层次(利用继承与派生机制)。然后以这些类为基础提出程序设计的思路和方法,包括对算法的设计。
在设计阶段并不牵涉任何一门具体的计算机语言,而是用一种更通用的描述工具(如伪代码或流程图)来描述
3 面向对象编程(object oriented programming,OOP)
根据面向对象设计的结果,选择一种计算机语言把它写成程序,可以是python
4 面向对象测试(object oriented test,OOT)
在写好程序后交给用户使用前,必须对程序进行严格的测试,测试的目的是发现程序中的错误并修正它。
面向对的测试是用面向对象的方法进行测试,以类作为测试的基本单元。
5 面向对象维护(object oriendted soft maintenance,OOSM)
正如对任何产品都需要进行售后服务和维护一样,软件在使用时也会出现一些问题,或者软件商想改进软件的性能,这就需要修改程序。
由于使用了面向对象的方法开发程序,使用程序的维护比较容易。
因为对象的封装性,修改一个对象对其他的对象影响很小,利用面向对象的方法维护程序,大大提高了软件维护的效率,可扩展性高。
在面向对象方法中,最早发展的肯定是面向对象编程(OOP),那时OOA和OOD都还没有发展起来,因此程序设计者为了写出面向对象的程序,还必须深入到分析和设计领域,尤其是设计领域,那时的OOP实际上包含了现在的OOD和OOP两个阶段,这对程序设计者要求比较高,许多人感到很难掌握。
现在设计一个大的软件,是严格按照面向对象软件工程的5个阶段进行的,这个5个阶段的工作不是由一个人从头到尾完成的,而是由不同的人分别完成,这样OOP阶段的任务就比较简单了。程序编写者只需要根据OOd提出的思路,用面向对象语言编写出程序既可。