一、面向对象的程序设计
1.面向过程 VS 面向对象
(1)编程范式
编程是程序员用特定的语法+数据结构+算法组成的代码来告诉计算机如何执行任务的过程,一个程序是程序员为了得到一个任务结果而编写的一组指令的集合,正所谓条条大路通罗马,实现一个任务的方式有很多种不同的方式,对这些不同的编程方式的特点进行归纳总结得出来的编程方式类别,即为编程范式。不同的编程范式本质上代表对各种类型的任务采取的不同的解决问题的思路,大多数语言只支持一种编程范式,当然也有些语言可以同时支持多种编程范式。两种最重要的编程范式分别是面向过程编程和面向对象编程。
(2)面向过程编程(Procedural Programming)
面向过程编程依赖——你猜到了procedures,一个procedure包含一组要被进行计算的步骤,面向过程又被称为top-downlanguages,就是程序从上到下一步步执行,一步步从上到下,从头到尾的解决问题。基本设计思路就是程序一开始是要着手解决一个大的问题,然后把一个大问题分解成很多个小问题或子过程,这些子过程再执行的过程再继续分解直到小问题足够简单到可以在一个小步骤范围内解决。
举个典型的面向过程的例子, 数据库备份, 分三步,连接数据库,备份数据库,测试备份文件可用性。
代码如下:
def db_conn():
print("connecting db...")
def db_backup(dbname):
print("导出数据库...",dbname)
print("将备份文件打包,移至相应目录...")
def db_backup_test():
print("将备份文件导入测试库,看导入是否成功")
def main():
db_conn()
db_backup('my_db')
db_backup_test()
if __name__ == '__main__':
main()
这样做的问题也是显而易见的,就是如果你要对程序进行修改,对你修改的那部分有依赖的各个部分你都也要跟着修改。
所以我们一般认为, 如果你只是写一些简单的脚本,去做一些一次性任务,用面向过程的方式是极好的,但如果你要处理的任务是复杂的,且需要不断迭代和维护的,那还是用面向对象最方便了。
优点:极大的降低了程序的复杂度
缺点是:一套流水线或者流程就是用来解决一个问题,生产汽水的流水线无法生产汽车,即便是能,也得是大改,改一个组件,牵一发而动全身。
应用场景:一旦完成基本很少改变的场景,著名的例子有Linux內核,git,以及Apache HTTP Server等。
(3)面向对象编程
OOP编程是利用“类”和“对象”来创建各种模型来实现对真实世界的描述,使用面向对象编程的原因一方面是因为它可以使程序的维护和扩展变得更简单,并且可以大大提高程序开发效率,另外,基于面向对象的程序可以使它人更加容易理解你的代码逻辑,从而使团队开发变得更从容。
优点:解决了程序的扩展性。对某一个对象单独修改,会立刻反映到整个体系中,如对游戏中一个人物参数的特征和技能修改都很容易。
缺点:可控性差,无法向面向过程的程序设计流水线式的可以很精准的预测问题的处理流程与结果,面向对象的程序一旦开始就由对象之间的交互解决问题,即便是上帝也无法预测最终结果。于是我们经常看到一个游戏人某一参数的修改极有可能导致阴霸的技能出现,一刀砍死3个人,这个游戏就失去平衡。
应用场景:需求经常变化的软件,一般需求的变化都集中在用户层,互联网应用,企业内部软件,游戏等都是面向对象的程序设计大显身手的好地方。
面向对象的程序设计并不是全部。对于一个软件质量来说,面向对象的程序设计只是用来解决扩展性。
2.面向对象编程(Object-Oriented Programming )介绍
对于编程语言的初学者来讲,OOP不是一个很容易理解的编程方式,大家虽然都按老师讲的都知道OOP的三大特性是继承、封装、多态,并且大家也都知道了如何定义类、方法等面向对象的常用语法,但是一到真正写程序的时候,还是很多人喜欢用函数式编程来写代码,特别是初学者,很容易陷入一个窘境就是“我知道面向对象,我也会写类,但我依然没发现在使用了面向对象后,对我们的程序开发效率或其它方面带来什么好处,因为我使用函数编程就可以减少重复代码并做到程序可扩展了,为啥子还用面向对象?”。 对于此,我个人觉得原因应该还是因为你没有充分了解到面向对象能带来的好处。
(1)面向对象编程的好处
无论用什么形式来编程,我们都要明确记住以下原则:
- 写重复代码是非常不好的低级行为
- 你写的代码需要经常变更
开发正规的程序跟那种写个运行一次就扔了的小脚本一个很大不同就是,你的代码总是需要不断的更改,不是修改bug就是添加新功能等,所以为了日后方便程序的修改及扩展,你写的代码一定要遵循易读、易改的原则(专业数据叫可读性好、易扩展)。
如果你把一段同样的代码复制、粘贴到了程序的多个地方以实现在程序的各个地方调用这个功能,那日后你再对这个功能进行修改时,就需要把程序里多个地方都改一遍,这种写程序的方式是有问题的,因为如果你不小心漏掉了一个地方没改,那可能会导致整个程序的运行都 出问题。 因此我们知道 在开发中一定要努力避免写重复的代码,否则就相当于给自己再挖坑。
还好,函数的出现就能帮我们轻松的解决重复代码的问题,对于需要重复调用的功能,只需要把它写成一个函数,然后在程序的各个地方直接调用这个函数名就好了,并且当需要修改这个功能时,只需改函数代码,然后整个程序就都更新了。
函数编程与OOP的主要区别就是OOP可以使程序更加容易扩展和易更改。
(2)面向对象的核心特性
Class 类
一个类即是对一类拥有相同属性的对象的抽象、蓝图、原型。在类中定义了这些对象的都具备的属性(variables(data))、共同的方法。
Object 对象
一个对象即是一个类的实例化后实例,一个类必须经过实例化后方可在程序中调用,一个类可以实例化多个对象,每个对象亦可以有不同的属性,就像人类是指所有人,每个人是指具体的对象,人与人之前有共性,亦有不同。
Encapsulation 封装
在类中对数据的赋值、内部调用对外部用户是透明的,这使类变成了一个胶囊或容器,里面包含着类的数据和方法。
Inheritance 继承
一个类可以派生出子类,在这个父类里定义的属性、方法自动被子类继承。
Polymorphism 多态
多态是面向对象的重要特性,简单点说:“一个接口,多种实现”,指一个基类中派生出了不同的子类,且每个子类在继承了同样的方法名的同时又对父类的方法做了不同的实现,这就是同一种事物表现出的多种形态。
编程其实就是一个将具体世界进行抽象化的过程,多态就是抽象化的一种体现,把一系列具体事物的共同点抽象出来, 再通过这个抽象的事物, 与不同的具体事物进行对话。
对不同类的对象发出相同的消息将会有不同的行为。比如,你的老板让所有员工在九点钟开始工作,他只要在九点钟的时候说:“开始工作”即可,而不需要对销售人员说:“开始销售工作”,对技术人员说:“开始技术工作”, 因为“员工”是一个抽象的事物, 只要是员工就可以开始工作,他知道这一点就行了。至于每个员工,当然会各司其职,做各自的工作。
多态允许将子类的对象当作父类的对象使用,某父类型的引用指向其子类型的对象,调用的方法是该子类型的方法。这里引用和调用方法的代码编译前就已经决定了,而引用所指向的对象可以在运行期间动态绑定。
(3)OOP实例演示
模拟CS设计
class Role(object):
def __init__(self,name,role,weapon,life_value=100,money=15000):
self.name = name
self.role = role
self.weapon = weapon
self.life_value = life_value
self.money = money
def shot(self):
print("shooting...")
def got_shot(self):
print("ah...,I got shot...")
def buy_gun(self,gun_name):
print("just bought %s" %gun_name)
r1 = Role('Alex','police','AK47’) #生成一个角色
r2 = Role('Jack','terrorist','B22’) #生成一个角色
二、类和对象
1.什么是对象,什么是类
提示:python的class术语与c++有一定区别,与 Modula-3更像。
python中一切皆为对象,且python3统一了类与类型的概念,类型就是类,所以,不管你信不信,你已经使用了很长时间的类了
>>> dict #类型dict就是类dict
<class 'dict'>
>>> d=dict(name='egon') #实例化
>>> d.pop('name') #向d发一条消息,执行d的方法pop
'egon'
基于面向对象设计一款游戏:英雄联盟,每个玩家选一个英雄,每个英雄都有自己的特征和和技能,特征即数据属性,技能即方法属性,特征与技能的结合体就一个对象。
从一组对象中提取相似的部分就是类,类是所有对象都具有的特征和技能的结合体。
在python中,用变量表示特征,用函数表示技能,因而类是变量与函数的结合体,对象是变量与方法(指向类的函数)的结合体。
garen_hero.Q() #称为向garen_hero这个对象发送了一条消息,让他去执行Q这个函数,完成一个功能,类似的有:
garen_hero.W()
garen_hero.E()
garen_hero.R()
一个英雄可以攻击另外一个英雄,这就是对象之间的交互
garen_hero.attack(Riven)
2.类的使用
类中的函数第一个参数必须是self
类中定义的函数叫做“方法”
示例1:
class Garen: #定义英雄盖伦的类,不同的玩家可以用它实例出自己英雄;
camp='Demacia' #所有玩家的英雄(盖伦)的阵营都是Demacia;
def attack(self,enemy): #普通攻击技能,enemy是敌人;
enemy.life_value-=self.aggressivity #根据自己的攻击力,攻击敌人就减掉敌人的生命值。
示例2:
class Chinese:
country = 'China'
def __init__(self,name,age):
self.name=name #p1.name='egon'
self.age=age #p1.age=18
def talk(self):
print('say chinese',self)
类的第一种用法:实例化
p1=Chinese('egon',18) #__init__(p1,'egon',18)
类的第二种用法:属性引用(增删改查)
print(Chinese.country) #类的数据属性
print(Chinese.__init__) #类的函数属性
Chinese.__init__(1,2,3) #1.name=2
print(Chinese.__dict__) #查看类的属性字典,或者说名称空间
print(Chinese.country)
print(Chinese.__dict__['country'])
3.类的区别
(1)只有在python2中才分新式类和经典类,python3中统一都是新式类
(2)新式类和经典类声明的最大不同在于,所有新式类必须继承至少一个父类
(3)所有类甭管是否显式声明父类,都有一个默认继承object父类
在python2中的区分
经典类:
class 类名:
pass
新式类:
class 类名(父类):
pass
在python3中,上述两种定义方式全都是新式类。
4.类有两种作用
(1)属性引用(类名.属性)
>>> Garen.camp #引用类的数据属性,该属性与所有对象/实例共享
'Demacia'
>>> Garen.attack #引用类的函数属性,该属性也共享
<function Garen.attack at 0x101356510>
>>> Garen.name='Garen' #增加属性
>>> del Garen.name #删除属性
(2)实例化(__init__与self)
class Garen: #定义英雄盖伦的类,不同的玩家可以用它实例出自己英雄;
camp='Demacia' #所有玩家的英雄(盖伦)的阵营都是Demacia;
def __init__(self,nickname,aggressivity=58,life_value=455): #英雄的初始攻击力58...;
self.nickname=nickname #为自己的盖伦起个别名;
self.aggressivity=aggressivity #英雄都有自己的攻击力;
self.life_value=life_value #英雄都有自己的生命值;
def attack(self,enemy): #普通攻击技能,enemy是敌人;
enemy.life_value-=self.aggressivity #根据自己的攻击力,攻击敌人就减掉敌人的生命值。
>>> g1=Garen('草丛伦') #就是在执行Garen.__init__(g1,'草丛伦'),然后执行__init__内的代码g1.nickname=‘草丛伦’等
self的作用是在实例化时自动将对象/实例本身传给__init__的第一个参数,self可以是任意名字。
根据上图我们得知,其实self,就是实例本身!你实例化时python会自动把这个实例本身通过self参数传进去。
上面的这个__init__()叫做初始化方法(或构造方法),在类被调用时,这个方法(虽然它是函数形式,但在类中就不叫函数了,叫方法会自动执行,进行一些初始化的动作。
- 我们定义的类的属性到底存到哪里了?有两种方式查看
dir(类名):查出的是一个名字列表
类名.__dict__:查出的是一个字典,key为属性名,value为属性值 - 特殊的类属性
类名.__name__ #类的名字(字符串)
类名.__doc__ #类的文档字符串
类名.__base__ #类所有父类构成的元组
类名.__dict__ #类的字典属性
类名.__module__ #类定义所在的模块
类名.__class__ #实例对应的类(仅新式类中)
5.对象
对象是关于类而实际存在的一个例子,即实例。
>>> g1=Garen('草丛伦') #类实例化得到g1这个实例,基于该实例我们讲解实例相关知识
>>> type(g1) #查看g1的类型就是类Garen
<class '__main__.Garen'>
>>> isinstance(g1,Garen) #g1就是Garen的实例
True
6.对象/实例只有一种作用:属性引用
#对象/实例本身其实只有数据属性
>>> g1.nickname
'草丛伦'
>>> g1.aggressivity
58
>>> g1.life_value
455
查看实例属性:dir和内置__dict__两种方式
特殊实例属性
__class__
__dict__
对象/实例本身只有数据属性,但是python的class机制会将类的函数绑定到对象上,称为对象的方法,或者叫绑定方法,绑定方法唯一绑定一个对象,同一个类的方法绑定到不同的对象上,属于不同的方法,内存地址都不会一样
>>> g1.attack #对象的绑定方法
<bound method Garen.attack of <__main__.Garen object at 0x101348dd8>>
>>> Garen.attack #对象的绑定方法attack本质就是调用类的函数attack的功能,二者是一种绑定关系
<function Garen.attack at 0x101356620>
对象的绑定方法的特别之处在于:obj.func()会把obj传给func的第一个参数。
7.对象之间的交互
class Riven:
camp='Noxus' #所有玩家的英雄(锐雯)的阵营都是Noxus;
def __init__(self,nickname,aggressivity=54,life_value=414): #英雄的初始攻击力54;
self.nickname=nickname #为自己的锐雯起个别名;
self.aggressivity=aggressivity #英雄都有自己的攻击力;
self.life_value=life_value #英雄都有自己的生命值;
def attack(self,enemy): #普通攻击技能,enemy是敌人;
enemy.life_value-=self.aggressivity #根据自己的攻击力,攻击敌人就减掉敌人的生命值。
实例出一个Riven来
>>> r1=Riven('锐雯雯')
交互:锐雯雯攻击草丛伦,反之一样
>>> g1.life_value
455
>>> r1.attack(g1)
>>> g1.life_value
401
8.类名称空间与对象/实例名称空间
创建一个类就会创建一个类的名称空间,用来存储类中定义的所有名字,这些名字称为类的属性。
而类有两种属性:数据属性和函数属性
其中类的数据属性是共享给所有对象的
>>> id(r1.camp) #本质就是在引用类的camp属性,二者id一样
4315241024
>>> id(Riven.camp)
4315241024
而类的函数属性是绑定到所有对象的:
>>> id(r1.attack)
4302501512
>>> id(Riven.attack)
4315244200
r1.attack就是在执行Riven.attack的功能,python的class机制会将Riven的函数属性attack绑定给r1,r1相当于拿到了一个指针,指向Riven类的attack功能
除此之外r1.attack()会将r1传给attack的第一个参数
创建一个对象/实例就会创建一个对象/实例的名称空间,存放对象/实例的名字,称为对象/实例的属性。
在obj.name会先从obj自己的名称空间里找name,找不到则去类中找,类也找不到就找父类...最后都找不到就抛出异常。
三、练习
1.编写一个学生类,产生一堆学生对象,要求有一个计数器(属性),统计总共实例了多少个对象。
class Foo:
count=0
def __init__(self,name):
print(self.count)
# count+=1 #全局的count
# self.count=10 # 对象自己
Foo.count+=1
self.name=name
obj1=Foo('egon1') #Foo.count+=1 Foo.count=1
print(Foo.count)
print(obj1.count)
obj2=Foo('egon2') #Foo.count+=1 Foo.count=2
print(Foo.count)
print(obj2.count)
运行结果:
0 --print(self.count)
1 --print(Foo.count)
1 --print(obj1.count)
1
2
2
2.定义学生类并产生对象,引用对象属性
class Student:
tag=123123
def __init__(self,ID,name,age):
self.id=ID
self.name=name
self.age=age
def walk(self):
print('%s is walking' %self.name)
s1=Student(1,'egon',18)
s2=Student(2,'alex',10000)
print(s1.id)
print(s1.name)
print(s1.age)
print(s1.tag)
print(s2.tag)
s1.walk()
s2.walk()
运行结果:
1
egon
18
123123
123123
egon is walking
alex is walking
3.对象之间的交互:模拟英雄联盟攻击方式
定义锐雯类:
class Riven:
camp='Noxus'
def __init__(self,nickname,
aggressivity=54,
life_value=414,
money=1001,
armor=3):
self.nickname=nickname
self.aggressivity=aggressivity
self.life_value=life_value
self.money=money
self.armor=armor
def attack(self,enemy):
damage_value=self.aggressivity-enemy.armor
enemy.life_value-=damage_value
定义盖文类:
class Garen:
camp='Demacia'
def __init__(self,nickname,
aggressivity=58,
life_value=455,
money=100,
armor=10):
self.nickname=nickname
self.aggressivity=aggressivity
self.life_value=life_value
self.money=money
self.armor=armor
def attack(self,enemy):
damage_value=self.aggressivity-enemy.armor
enemy.life_value-=damage_value
定义装备:
class BlackCleaver:
def __init__(self,price=475,aggrev=9,life_value=100):
self.price=price
self.aggrev=aggrev
self.life_value=life_value
def update(self,obj):
obj.money-=self.price #减钱
obj.aggressivity+=self.aggrev #加攻击
obj.life_value+=self.life_value #加生命值
def fire(self,obj): #这是该装备的主动技能,喷火,烧死对方
obj.life_value-=1000 #假设火烧的攻击力是1000
测试交互:
r1=Riven('草丛伦')
g1=Garen('盖文')
b1=BlackCleaver()
print(r1.aggressivity,r1.life_value,r1.money) #r1的攻击力,生命值,护甲
if r1.money > b1.price:
r1.b1=b1
b1.update(r1)
print(g1.aggressivity,g1.life_value,g1.money) #g1的攻击力,生命值,护甲
print(g1.life_value) #显示g1的生命值
r1.attack(g1) #普通攻击
print(g1.life_value) #显示g1被攻击后的生命值
r1.b1.fire(g1) #用装备攻击
print(g1.life_value) #g1的生命值小于0就死了
运行结果:
54 414 1001
58 455 100
455
402
-598