一、编程范式
1,编程是程序员用特定的 语法+数据结构+算法 组成的代码来告诉计算机如何执行任务的过程 。
2,一个程序是程序员为了得到一个任务结果而编写的一组指令的集合,正所谓条条大路通罗马,实现一个任务有很多种不同的方式, 对这些不同的编程方式的特点进行归纳总结得出来的编程方式类别,即为编程范式。
3,不同的编程范式本质上代表对各种类型的任务采取的不同的解决问题的思路, 大多数语言只支持一种编程范式,当然也有些语言可以同时支持多种编程范式。
4,两种最重要的编程范式分别是面向过程编程和面向对象编程。
二、什么是面向对象的程序设计及为什么要有它
1,面向过程的程序设计
1)核心是过程二字,过程指的是解决问题的步骤,设计一条流水线,机械式的思维方式。
优点:复杂度的问题流程化,进而简单化(一个复杂的问题,分成一个个小的步骤去实现,实现小的步骤将会非常简单)
缺点:一套流水线或者流程就是用来解决一个问题,生产汽水的流水线无法生产汽车,即便是能,也得是大改,改一个组件,牵一发而动全身。
应用场景:一旦完成基本很少改变的场景,著名的例子有Linux內核,git,以及Apache HTTP Server等。
2)面向过程又称top-down languages, 就是程序从上到下一步步执行,一步步从上到下,从头到尾的解决问题 。
3)基本设计思路就是程序一开始是要着手解决一个大的问题,然后把一个大问题分解成很多个小问题或子过程,这些子过程再执行的过程再继续分解直到小问题足够简单到可以在一个小步骤范围内解决。
2,面向对象的程序设计
1)核心是对象(上帝式思维)二字,对象就是特征与技能的结合体。
(要理解对象为何物,必须把自己当成上帝,上帝眼里世间存在的万物皆为对象,不存在的也可以创造出来。面向对象的程序设计好比如来设计西游记,如来要解决的问题是把经书传给东土大唐,如来想了想解决这个问题需要四个人:唐僧,沙和尚,猪八戒,孙悟空,每个人都有各自的特征和技能(这就是对象的概念,特征和技能分别对应对象的数据属性和方法属性),然而这并不好玩,于是如来又安排了一群妖魔鬼怪,为了防止师徒四人在取经路上被搞死,又安排了一群神仙保驾护航,这些都是对象。然后取经开始,师徒四人与妖魔鬼怪神仙交互着直到最后取得真经。如来根本不会管师徒四人按照什么流程去取),对象是特征与技能的结合体,基于面向对象设计程序就好比在创造一个世界,你就是这个世界的上帝,存在的皆为对象,不存在的也可以创造出来,与面向过程机械式的思维方式形成鲜明对比,面向对象更加注重对现实世界的模拟,是一种“上帝式”的思维方式。
优点:
解决了程序的扩展性。对某一个对象单独修改,会立刻反映到整个体系中,如对游戏中一个人物参数的特征和技能修改都很容易。
缺点:
a.编程的复杂度远高于面向过程,不了解面向对象而立即上手基于它设计程序,极容易出现过度设计的问题。一些扩展性要求低的场景使用面向对象会徒增编程难度,比如管理linux系统的shell脚本就不适合用面向对象去设计,面向过程反而更加适合。
b. 无法向面向过程的程序设计流水线式的可以很精准的预测问题的处理流程与结果,面向对象的程序一旦开始就由对象之间的交互解决问题,即便是上帝也无法准确地预测最终结果。于是我们经常看到对战类游戏,新增一个游戏人物,在对战的过程中极容易出现阴霸的技能,一刀砍死3个人,这种情况是无法准确预知的,只有对象之间交互才能准确地知道最终的结果。
应用场景:
需求经常变化的软件,一般需求的变化都集中在用户层,互联网应用,企业内部软件,游戏等都是面向对象的程序设计大显身手的好地方。
2)面向对象的程序设计并不是全部。对于一个软件质量来说,面向对象的程序设计只是用来解决扩展性。
三、类与对象
1,类即类别、种类,是面向对象设计最重要的概念,对象是特征与技能的结合体,而类则是一系列对象相似的特征与技能的结合体。
2,那么,是先有的一个个具体存在的对象(比如一个具体存在的人),还是先有的人类这个概念,这个问题需要分两种情况去看。
1)在现实世界中:先有对象,再有类。
世界上肯定是先出现各种各样的实际存在的物体,然后随着人类文明的发展,人类站在不同的角度总结出了不同的种类,如:人类,动物类,植物类等概念。
也就是说,对象是具体存在的,而类仅仅只是一个概念,并不真实存在。
2)在程序中:要先定义类,在产生对象,顺序不能错。
这与函数的使用是类似的,先定义函数,后调用函数,类也是一样的,在程序中需要先定义类,后调用类实例化对象。
不一样的是,调用函数会执行函数体代码返回的是函数体执行的结果,而调用类会产生对象,返回的是对象。
3,按照上述步骤,定义一个类(大家都玩过英雄联盟(LOL)吧,我们站在LOL的角度去看,写一些简单的属性,方法)
# 在现实世界中,站在英雄联盟的角度:先有对象,再有类。 对象1:托儿所 特征(属性,变量): 游戏 = LOL 姓名 = 托儿所 性别 = 男 年龄 = 23 技能(方法,函数): 学习 睡觉 打架 对象2:儿童劫 特征: 游戏 = LOL 姓名 = 儿童劫 性别 = 男 年龄 = 24 技能: 学习 睡觉 打架 对象3:鱼尾雯 特征: 游戏 = LOL 姓名 = 鱼尾雯 性别 = 女 年龄18 技能: 学习 睡觉 打架 现实中的英雄类: 相似的特征: 游戏 = LOL 相似的技能: 学习 睡觉 打架
# 在程序中,必须先定义(类),后使用(产生对象) # 1.在程序中特征用变量标识,技能用函数标识 # 2.因而类中最常见的无非是:变量和函数的定义 # 程序中的类 class Lolhero: game = "LOL" def learn(self): print("is learning") def sleep(self): print("is sleeping") def attacking(self): print("is attacking") # 注意: # 1.类中可以有任意python代码,这些代码在类定义阶段便会执行 # 2.因而会产生新的名称空间,用来存放类的变量名与函数名,可以通过OldboyStudent.__dict__查看 # 3.对于经典类来说我们可以通过该字典操作类名称空间的名字(新式类有限制),但python为我们提供专门的.语法 # 4.点(万能的.)是访问属性的语法,类中定义的名字,都是类的属性 print(Lolhero.game) # 程序中类的用法 # .:专门用来访问属性,本质操作的就是__dict__ # Lolhero.game # 等于经典类的操作 Lolhero.__dict__['game'] # Lolhero.game='LOL' # 等于经典类的操作Lolhero.__dict__['game']='LOL' # Lolhero.x=1 # 等于经典类的操作Lolhero.__dict__['x']=1 # del Lolhero.x # 等于经典类的操作Lolhero.__dict__.pop('x') # 程序中的对象 # 调用类,或称为实例化,得到对象 l1 = Lolhero() l2 = Lolhero() l3 = Lolhero() # 如此,l1,l2,l3 都一样了,而这三者除了相似的属性之外,还有各种不同的属性,这就用到了 __init__。 # 注意:该方法是在对象产生之后才会执行,只用来为对象进行初始化操作,可以有任意代码,但一定不能有返回值。
# 类体中的代码在定义阶段不会执行,但是会被检测。 class Lolhero: # ...... def __init__(self,name,sex,age): self.name = name self.sex = sex self.age = age # ...... # 先调用类产生空对象 l1 ,然后调用 Lolhero.__init__(l1,"托儿所","男",23),把对象本身当做第一个参数 self 传进去 l1 = Lolhero("托儿所","男",23) l2 = Lolhero("儿童劫","男",24) l3 = Lolhero("鱼尾雯","女",18) # 程序中对象的用法 # 执行 __init__,l1.name = "托儿所",很明显也会产生对象的名称空间 print(l2.__dict__) # {'name': '儿童劫', 'sex': '男', 'age': 24} print(l2.name) # 儿童劫 l2.name = "娃娃鱼" print(l2.name) # 娃娃鱼 print(l1.__dict__) # {'name': '托儿所', 'sex': '男', 'age': 23} l1.ability = "斩钢闪" print(l1.__dict__) # {'name': '托儿所', 'sex': '男', 'age': 23, 'ability': '斩钢闪'} del l1.ability print(l1.__dict__) # {'name': '托儿所', 'sex': '男', 'age': 23}
!!!细说 __init__ 方法,为对象定制自己独有的特征!!!
#方式一、为对象初始化自己独有的特征 class People: country='China' x=1 def run(self): print('----->', self) # 实例化出三个空对象 obj1=People() obj2=People() obj3=People() # 为对象定制自己独有的特征 obj1.name='daimei' obj1.age=18 obj1.sex='female' obj2.name='uzi' obj2.age=24 obj2.sex='male' obj3.name='zixi' obj3.age=26 obj3.sex='male' # print(obj1.__dict__) # {'name': 'daimei', 'age': 18, 'sex': 'female'} # print(obj2.__dict__) # {'name': 'uzi', 'age': 24, 'sex': 'male'} # print(obj3.__dict__) # {'name': 'zixi', 'age': 26, 'sex': 'male'} # print(People.__dict__) # # {'__module__': '__main__', 'country': 'China', 'x': 1, 'run': <function People.run at 0x0000022917C9CA60>, '__dict__': <attribute '__dict__' of 'People' objects>, '__weakref__': <attribute '__weakref__' of 'People' objects>, '__doc__': None}
#方式二、为对象初始化自己独有的特征 class People: country='China' x=1 def run(self): print('----->', self) # 实例化出三个空对象 obj1=People() obj2=People() obj3=People() # 为对象定制自己独有的特征 def chu_shi_hua(obj, x, y, z): #obj=obj1,x='daimei',y=18,z='female' obj.name = x obj.age = y obj.sex = z chu_shi_hua(obj1,'daimei',18,'female') chu_shi_hua(obj2,'uzi',23,'male') chu_shi_hua(obj3,'zixi',24,'male')
#方式三、为对象初始化自己独有的特征 class People: country='China' x=1 def chu_shi_hua(obj, x, y, z): #obj=obj1,x='egon',y=18,z='male' obj.name = x obj.age = y obj.sex = z def run(self): print('----->', self) obj1=People() # print(People.chu_shi_hua) People.chu_shi_hua(obj1,'daimei',18,'female') obj2=People() People.chu_shi_hua(obj2,'uzi',23,'male') obj3=People() People.chu_shi_hua(obj3,'zixi',24,'male')
# 方式四、为对象初始化自己独有的特征 class People: country='China' x=1 def __init__(obj, x, y, z): #obj=obj1,x='egon',y=18,z='male' obj.name = x obj.age = y obj.sex = z def run(self): print('----->', self) obj1=People('daimei',18,'female') # People.__init__(obj1,'daimei',18,'female') obj2=People('uzi',23,'male') # People.__init__(obj2,'uzi',23,'male') obj3=People('zixi',24,'male') # People.__init__(obj3,'zixi',24,'male')
# __init__方法 # 强调: # 1、该方法内可以有任意的python代码 # 2、一定不能有返回值 class People: country='China' x=1 def __init__(obj, name, age, sex): #obj=obj1,x='daimei',y=18,z='female' # if type(name) is not str: # raise TypeError('名字必须是字符串类型') obj.name = name obj.age = age obj.sex = sex def run(self): print('----->', self) # obj1=People(3537,18,'male') # 报异常,名字必须是字符串类型 obj1=People('daimei',18,'female') print(obj1.run) # 绑定方法<bound method People.run of <__main__.People object at 0x0000024E634CAF98>> obj1.run() # People.run(obj1) # -----> <__main__.People object at 0x0000024E634CAF98> 调用函数 print(People.run) # <function People.run at 0x0000024E634CCAE8>
Tip:
1,站在不同的角度,定义出的类是截然不同的。
2,现实中的类并不完全等于程序中的类,比如现实中的公司类,在程序中有时需要拆分成部门类,业务类......
3,有时为了编程需求,程序中也可能会定义现实中不存在的类,如策略类,现实中并不存在,但是在程序中却是一个很常见的类。
# python 为类内置的特殊属性 类名.__name__ # 类的名字(字符串) 类名.__doc__ # 类的文档字符串 类名.__base__ # 类的第一个父类(看继承就懂了) 类名.__bases__ # 类所有父类构成的元组 类名.__dict__ # 类的字典属性 类名.__module__ # 类定义所在的模块 类名.__class__ # 实例对应的类(仅新式类中)
4,补充:从代码级别看面向对象
数据与专门操作该数据的功能组合到一起:
# 1,在没有学习类这个概念时,数据与功能是分离的 def exc1(host, port, db, charset): conn = connect(host, port, db, charset) conn = execute(sql) return xxx def exc2(host, port, db, charset,proc_name): conn = connect(host, port, db, charset) conn.call_proc(sql) return xxx # 每次调用都需要重复传入一堆参数 exc1('127.0.0.1',3306,'db1','utf8','select * from tb1;') exc2('127.0.0.1',3306,'db1','utf8','存储过程的名字')
#2、我们能想到的解决方法是,把这些变量都定义成全局变量 HOST="127.0.0.1" PORT=3306 DB="db1" CHARSET="utf8" def exc1(host,port,db,charset): conn=connect(host,port,db,charset) conn.execute(sql) return xxx def exc2(host,port,db,charset,proc_name) conn=connect(host,port,db,charset) conn.call_proc(sql) return xxx exc1(HOST,PORT,DB,CHARSET,'select * from tb1;') exc2(HOST,PORT,DB,CHARSET,'存储过程的名字')
""" 3、但是2的解决方法也是有问题的,按照2的思路,我们将会定义一大堆全局变量, 这些全局变量并没有做任何区分,即能够被所有功能使用,然而事实上只有HOST, PORT,DB,CHARSET是给exc1和exc2这两个功能用的。言外之意:我们必须找出 一种能够将数据与操作数据的方法组合到一起的解决方法,这就是我们说的类了。 """ class MySQLHandler: def __init__(self,host,port,db,charset='utf8'): self.host=host self.port=port self.db=db self.charset=charset def exc1(self,sql): conn=connect(self.host,self.port,self.db,self.charset) res=conn.execute(sql) return res def exc2(self,sql): conn=connect(self.host,self.port,self.db,self.charset) res=conn.call_proc(sql) return res obj=MySQLHandler('127.0.0.1',3306,'db1') obj.exc1('select * from tb1;') obj.exc2('存储过程的名字') #改进 class MySQLHandler: def __init__(self,host,port,db,charset='utf8'): self.host=host self.port=port self.db=db self.charset=charset self.conn=connect(self.host,self.port,self.db,self.charset) def exc1(self,sql): return self.conn.execute(sql) def exc2(self,sql): return self.conn.call_proc(sql) obj=MySQLHandler('127.0.0.1',3306,'db1') obj.exc1('select * from tb1;') obj.exc2('存储过程的名字')
四、属性查找
类有两种属性:数据属性和函数属性
1,类的数据属性是所有对象共享的
2,类的函数属性是绑定给对象用的
class Lolhero: game = "LOL" def learn(self): print("is learning") def sleep(self): print("is sleeping") def attacking(self): print("is attacking") l1= Lolhero() l2= Lolhero() l3= Lolhero() # 类的数据属性是所有对象共享的,id 都一样 print(id(Lolhero.game)) print(id(l1.game)) print(id(l2.game)) print(id(l3.game)) """ 2665454281760 2665454281760 2665454281760 2665454281760 """ # 类的函数属性是绑定给对象使用的. # ps:id是python的实现机制,并不能真实反映内存地址,如果有内存地址,还是以内存地址为准 print(Lolhero.learn) print(l1.learn) print(l2.learn) print(l3.learn) """ <function Lolhero.learn at 0x00000214C4EFCA60> <bound method Lolhero.learn of <__main__.Lolhero object at 0x00000214C4EFA7F0>> <bound method Lolhero.learn of <__main__.Lolhero object at 0x00000214C4EFA860>> <bound method Lolhero.learn of <__main__.Lolhero object at 0x00000214C4EFA898>> """
3,属性查找顺序
class People: y = 0 class Lolhero(People): game = "LOL" x = 1 def __init__(self,name,sex,age): self.name = name self.sex = sex self.age = age def learn(self): print("is learning") def sleep(self): print("is sleeping") def attacking(self): print("is attacking") l1 = Lolhero("托儿所","男",23) # 属性查找顺序 print(l1.name) # 现在对象中找 print(l1.x) # 对象中没有的话,去其类中找 print(l1.y) # 类中没有的话,去它的父类中找 print(l1.z) # 父类没有,会直接报错 """ 托儿所 1 0 AttributeError: 'Lolhero' object has no attribute 'z' """
五、绑定到对象方法的特殊之处
class Lolhero: game = "LOL" def __init__(self,name,sex,age): self.name = name self.sex = sex self.age = age def learn(self): print("%s is learning" % self.name) # 新增 self.name def sleep(self): print("%s is sleeping" % self.name) def attacking(self): print("%s is attacking" % self.name) l1 = Lolhero("托儿所","男",23) l2 = Lolhero("儿童劫","男",25) l3 = Lolhero("鱼尾雯","女",18)
类中定义的函数(没有被任何装饰器装饰的)是类的函数属性,类可以使用,但必须遵循函数的参数规则,有几个参数需要传几个参数。
Lolhero.learn(l1) # 托儿所 is learning Lolhero.learn(l2) # 儿童劫 is learning Lolhero.learn(l3) # 鱼尾雯 is learning
类中定义的函数(没有被任何装饰器装饰的),其实主要是给对象使用的,而且是绑定到对象的,虽然所有对象指向的都是相同的功能,但是绑定到不同的对象就是不同的绑定方法。
强调:绑定到对象的方法的特殊之处在于,绑定给谁就由谁来调用,谁来调用,就会将‘谁’本身当做第一个参数传给方法,即自动传值(方法__init__也是一样的道理)
l1.learn() # 等同于Lolhero.learn(l1) l2.learn() # 等同于Lolhero.learn(l2) l3.learn() # 等同于Lolhero.learn(l3)
注意:绑定到对象的方法的这种自动传值的特征,决定了在类中定义的函数都要默认写一个参数self,self可以是任意名字,但是约定俗成地写出self。
类即类型
提示:python 的 class 术语与 C++ 有一定的区别,与 Modula-3 更像。
python 中一切皆对象,且 python3 中类与类型是一个概念,类型就是类。
#类型dict就是类dict >>> list <class 'list'> #实例化的到3个对象l1,l2,l3 >>> l1=list() >>> l2=list() >>> l3=list() #三个对象都有绑定方法append,是相同的功能,但内存地址不同 >>> l1.append <built-in method append of list object at 0x10b482b48> >>> l2.append <built-in method append of list object at 0x10b482b88> >>> l3.append <built-in method append of list object at 0x10b482bc8> #操作绑定方法l1.append(3),就是在往l1添加3,绝对不会将3添加到l2或l3 >>> l1.append(3) >>> l1 [3] >>> l2 [] >>> l3 [] #调用类list.append(l3,111)等同于l3.append(111) >>> list.append(l3,111) #l3.append(111) >>> l3 [111]
六、对象之间的交互
class Suosuo: # 定义英雄亚索的类,不同的玩家可以用它实例出自己的英雄; camp= "Ionia" # 所有玩家的英雄(亚索)的阵营都是Ionia; def __init__(self,nickname,aggressivity=58,life_value=580): self.nickname = nickname self.aggressivity = aggressivity self.life_value = life_value def attack(self,enemy): # 普攻,enemy是敌人 enemy.life_value -= self.aggressivity # 攻击敌人,敌人的血量就会相应地减少
我们可以仿照 Suosuo 类再创建一个 Nuoshou 类。
class Nuoshou: # 定义英雄诺手的类,不同的玩家可以用它实例出自己的英雄; camp= "Noxux" # 所有玩家的英雄(诺手)的阵营都是Noxux; def __init__(self,nickname,aggressivity=60,life_value=680): self.nickname = nickname self.aggressivity = aggressivity self.life_value = life_value def attack(self,enemy): # 普攻,enemy是敌人 enemy.life_value -= self.aggressivity # 攻击敌人,敌人的血量就会相应地减少
实例出俩英雄
s1 = Suosuo("托儿所") n1 = Nuoshou("小学生之手")
交互:诺手攻击亚索,反之一样
print(s1.life_value) # 亚索初始生命值 n1.attack(s1) print(s1.life_value) # 被攻击后的生命值 """ 580 520 """
补充:
Q 是亚索的技能,在程序中我们把 Q 做成它的方法。
s1.Q() 的意思就是,向 s1 这个对象发送一条消息,让它去执行 Q 这个功能,类似的有:
s1.W() s1.E() s1.R()
七、练习
基于面向对象设计一个对战游戏
定义瑞文类:
定义盖伦类:
定义装备:
实现交互