目录
一. 引子:模板
二. 面向过程与面向对象
(1)面向过程编程
(2)面向对象编程
三. 对象与类
(1)对象
(2)类
四. 面向对象程序设计的特点:封装,继承,多态
(1)封装
(2)继承
(3)多态
一. 引子:模板
设想我们现在编写一款人狗大战的游戏。在人狗大战中,我们首先要描述狗和人。例如,对于一条狗来说,它有姓名,品种,狗的攻击力等等。我们可以用字典中的键值对去表示它的属性:
dog1 = {
"name":"zhaopeng", # 姓名
"d_type":"二哈", # 品种
"attack_val":30, # 攻击值
"life_val":50, # 生命值
}
print(dog1)
# {'name': 'zhaopeng', 'd_type': '二哈', 'attack_val': 30, 'life_val': 50}
为了方便复用,我们可以把它写成函数,这样每次传入参数即可:
def dog(name,d_type,attack_val): # 模板
data = {
"name":name,
"d_type":d_type,
"attack_val":attack_val,
"life_val":100, # 设置为默认值
}
return data
dog1 = dog("zhaopeng","二哈",30) # 生成实体
dog2 = dog("zhangsan","藏獒",50)
print(dog1, dog2)
# {'name': 'zhaopeng', 'd_type': '二哈', 'attack_val': 30, 'life_val': 100}
# {'name': 'zhangsan', 'd_type': '藏獒', 'attack_val': 50, 'life_val': 100}
同理我们可以写出人的属性模板。此外,我们还可以继续复杂化这个函数。例如我们可以把狗的品种和攻击力建立一个一一对应的联系,把人的年龄和攻击力也建立一个一一对应的关系:
# 将狗的品种与攻击值建立联系
attack_val = {
"二哈":20,
"藏獒":40,
"小奶狗":5
}
def dog(name,d_type): # 狗的属性模板
data = {
"name":name,
"d_type":d_type,
"life_val":100, # 设置为默认值
}
if d_type in attack_val:
data["attack_val"] = attack_val[d_type] # 在attack_val字典中查询键值对
else:
data["attack_val"] = 15
return data
def person(name,age): # 人的属性模板
data = {
"name":name,
"age":age,
"life_val":100, # 设置为默认值
}
if age > 18: # 根据人的年龄推算战斗力
data["attack_val"] = 50
else:
data["attack_val"] = 30
return data
dog1 = dog("zhaopeng","二哈") # 生成狗实体
dog2 = dog("zhangsan","藏獒")
person1 = person("lisi",30) # 生成人实体
person2 = person("wangwu",15)
print(dog1, dog2)
# {'name': 'zhaopeng', 'd_type': '二哈', 'life_val': 100, 'attack_val': 20}
# {'name': 'zhangsan', 'd_type': '藏獒', 'life_val': 100, 'attack_val': 40}
print(person1, person2)
# {'name': 'lisi', 'age': 30, 'life_val': 100, 'attack_val': 50}
# {'name': 'wangwu', 'age': 15, 'life_val': 100, 'attack_val': 30}
现在我们编写狗咬人和人咬狗的函数来模拟它们互相攻击:
def bite(dog, person): # 狗咬人
person["life_val"] -= dog["attack_val"]
print("狗[%s]咬了[%s]人一口,人的剩余血量是[%s]"%(dog["name"],person["name"],person["life_val"]))
def beat(dog, person): # 人打狗
dog["life_val"] -= person["attack_val"]
print("人[%s]揍了[%s]狗一下,狗的剩余血量是[%s]"%(person["name"],dog["name"],dog["life_val"]))
bite(dog1, person1)
# 狗[zhaopeng]咬了[lisi]人一口,人的剩余血量是[80]
beat(dog1, person2)
# 人[wangwu]揍了[zhaopeng]狗一下,狗的剩余血量是[70]
这样写当然看起来没有问题,然而我们如果不小心写反了人和狗,例如:
bite(person1, dog1)
这个时候自然会出现问题。我们希望实现bite函数只能是“狗咬人”,beat函数只能是“人打狗”。我们希望给人和狗增加对应的动作。当然,我们可以在函数体里面增加if...else判断语句,但当游戏动作变的很多的时候这种方法也不好。我们可以把bite函数和beat函数内嵌到人和狗的属性里面:
def dog(name,d_type): # 狗的属性模板
data = {
"name":name,
"d_type":d_type,
"life_val":100, # 设置为默认值
}
if d_type in attack_val:
data["attack_val"] = attack_val[d_type] # 在attack_val字典中查询键值对
else:
data["attack_val"] = 15
def bite(person): # 狗咬人
person["life_val"] -= data["attack_val"]
print("狗[%s]咬了[%s]人一口,人的剩余血量是[%s]"%(data["name"],person["name"],person["life_val"]))
data["bite"] = bite # 为了在函数外部调用此方法
return data
def person(name,age): # 人的属性模板
data = {
"name":name,
"age":age,
"life_val":100, # 设置为默认值
}
if age > 18: # 根据人的年龄推算战斗力
data["attack_val"] = 50
else:
data["attack_val"] = 30
def beat(dog): # 人打狗
dog["life_val"] -= data["attack_val"]
print("人[%s]揍了[%s]狗一下,狗的剩余血量是[%s]"%(data["name"],dog["name"],dog["life_val"]))
data["beat"] = beat # 为了在函数外部调用此方法
return data
dog1 = dog("zhaopeng","二哈") # 生成狗实体
dog2 = dog("zhangsan","藏獒")
person1 = person("lisi",30) # 生成人实体
person2 = person("wangwu",15)
dog1["bite"](person1) # 狗[zhaopeng]咬了[lisi]人一口,人的剩余血量是[80]
person2["beat"](dog1) # 人[wangwu]揍了[zhaopeng]狗一下,狗的剩余血量是[70]
dog2["beat"](person1) # KeyError: 'beat'
这样,我们就实现了限制人只能用人自己的功能,狗只能用狗自己的功能啦。
说了这么多,这跟面向对象有什么关系么? 当然有,其实你上面写的代码,就是面向对象的代码。你在设计角色时,为了让一个角色可以变成多个实体对象,你设计了一个基础模板,只要传入不同参数,就会产生不同的狗。这代表你已经开始切换成上帝视角看事情,上帝视角就是面向对象编程的视角,上帝要造世界万物,他肯定不是一个一个的造出来,他肯定是设计出一个个的物种的模板,然后通过模子批量批一个个的实体造出来。造出来的实体各有特色,属性、功能都不尽相同,有的人的贪婪、有的人好色、有的人懦弱,有的人勇猛。这些人之间会发生什么关系,谁和谁打仗,上帝懒的管,上帝只控制大局。我们接下来一点点接晓怎么通过面向对象在编程世界里做上帝。
二. 面向过程与面向对象
在介绍面向过程与面向对象之前,先介绍一下什么是编程范式:
编程是程序员用特定的语法+数据结构+算法组成的代码来告诉计算机如何执行任务的过程,一个程序是程序员为了得到一个任务结果而编写的一组指令的集合。正所谓条条大路通罗马,实现一个任务的方式有很多种不同的方式,对这些不同的编程方式的特点进行归纳总结出来的编程方式类别,即为编程范式。
不同的编程范式本质上代表对各种类型的任务采取的不同的解决问题的思路,大多数语言只支持一种编程范式,当然也有些语言可以同时支持多种编程范式。 两种最重要的编程范式分别是面向过程遍程和面向对象编程。
(1)面向过程编程
Procedural programming uses a list of instructions to tell the computer what to do step-by-step.面向过程编程依赖procedures。一个procedure包含一组要被进行计算的步骤,面向过程又被称为top-down languages,就是程序从上到下一步步执行,从头到尾的解决问题。
面向过程的基本设计思路就是程序一开始是要着手解决一个大的问题,然后把一个大问题分解成很多个小问题或子过程,这些子过程再执行的过程再继续分解直到小问题足够简单到可以在一个小步骤范围内解决。
举个典型的面向过程的例子,把大象装冰箱,整个流程分以下几步:
- 把冰箱门打开,open()
- 把大象装入冰箱,push()
- 把冰箱门关上,close()
我们只需要编写每一步的代码,然后连起来即可。然而,这样做的问题也是显而易见的,就是如果你要对程序进行修改,对你修改的那部分有依赖的各个部分你都也要跟着修改。举个例子,现在要编写:
- 把老虎装进冰箱
- 把大象装进皮箱
- 把老虎装进冰箱,但不关冰箱门
如果程序开头设置了一个变量值为1,但如果其它子过程依赖这个值为1的变量才能正常运行,如果修改了这个变量,那这个子过程你也要修改,假如又有一个其它子程序依赖这个子过程,那就会发生一连串的影响,随着程序项目越来越大,这种编程方式的维护难度会越来越高。
所以我们一般认为, 如果你只是写一些简单的脚本,去做一些一次性任务,用面向过程的方式是极好的,但如果你要处理的任务是复杂的,且需要不断选代和维护的,那还是用面向对象最方便了。
(2)面向对象编程
同样是上面的问题,我们抽象出来两个类:
- 冰箱类,它具有open()和close()方法
- 大象类,它具有走进冰箱的enter()方法
这样每次修改需求,就只需重新把类具象化即可。例如仍然对于大象装冰箱的问题可以写成:
- 冰箱.open()
- 大象.enter()
- 冰箱.close()
如果修改需求为老虎装冰箱,则第二行修改为老虎.enter()即可。
以上程序设计方法就是面向对象的程序设计。面向对象(object oriented)的英文缩写是OO,它是一种设计思想。从20世纪60年代提出面向对象的概念到现在,它已经发展成为一种比较成熟的编程思想,并且逐步成为当前软件开发领域的主流技术。如我们经常听说的面向对象编程(object oriented prgramming,OOP)就是主要针对大型软件设计而提出的,它可以使软件设计更加灵活,并且能更好地进行代码复用。
面向对象中的对象(object),通常是指客观世界中存在的对象。这个对象具有唯一性,对象之间各不相同,各有各的特点,每个对象都有自己的运动规律和内部状态;对象之间又是可以相互联系相互作用的。另外,对象也可以是一个抽象的事物。例如,可以从圆形、正方形、三角形等图形抽象出一个简单图形,简单图形就是一个对象,它有自己的属性和行为,图形中边的个数是它的属性,图形的面积也是它的属性,输出图形的面积就是它的行为。概括地讲,面向对象技术是一种从组织结构上模拟客观世界的方法。
三. 对象与类
(1)对象
对象是一种抽象概念,表示任意存在的事物。世间万物皆对象。现实世界中,随处可见的一个事物就是对象,对象是事物存在的实体。如上面引子里面提到的狗。通常将对象划分为两部分,即静态部分与动态部分。静态部分被称为“属性”,任何对象都具备自身属性,这些属性不仅是客观存在的,而且是不能被忽视的,如狗的名字,品种,初始血值,攻击力等。动态部分指的是对象的行为,即对象执行的动作,如在人狗大战中狗可以咬人。
在Python中,一切都是对象。也就是说,不仅是具体的事物被称为对象,字符串、函数等也都被称为对象。这说明Python天生就是面向对象的。
(2)类
类是封装对象的属性和行为的载体,反过来说,具有相同属性和行为的一类实体被称为类。例如,和雁群比作大雁类,那么大雁类就具备了喙、翅膀和爪等属性,觅食、飞行和睡觉等行为,而一只要从北方飞往南方的大雁则被视为大雁类的一个对象。
再例如引子中的人狗大战,狗类具有姓名,品种,初始血值,攻击力等属性,咬人的行为;一只具体的狗
{'name': 'zhaopeng', 'd_type': '二哈', 'life_val': 100, 'attack_val': 20}
被视为狗类的一个对象。
四. 面向对象程序设计的特点:封装,继承,多态
(1)封装
封装是面向对象编程的核心思想,将对象的属性和行为封装起来,而将对象的属性和行为封装起来的载体就是类,类通常对客户隐藏其实现细节,这就是封装的思想。例如,用户使用计算机,只需要按键盘就可以实现一些功能,而无须知道计算机内部是如何工作的。
采用封装思想保证了类内部数据结构的完整性,使用该类的用户不能直接看到类中的数据结构,而只能执行类允许公开的数据,这样就避免了外部对内部数据的影响,提高了程序的可维护性。
(2)继承
矩形、菱形、平行四边形和梯形等都是四边形。因为四边形与它们具有共同的特征,即拥有4个边。只要将四边形适当地延伸,就会得到上述图形。以平行四边形为例,如果把平行四边形看作四边形的延伸,那么平行四边形就复用了四边形的属性和行为,同时添加了平行四边形特有的属性和行为如平行四边形的对边平行且相等。
其中将类似于平行四边形的类称为子类,将类似于四边形的类称为父类或超类。值得注意的是,在述平行四边形和四边形的关系时,可以说平行四边形是特殊的四边形,但不能说四边形是平行四边形。同理,Python中可以说子类的实例都是父类的实例,但不能说父类的实例是子类的实例。
综上所述,继承是实现代码重复利用的重要手段,子类通过继承复用了父类的属性和行为的同时,又添加了子类特有的属性和行为。
(3)多态
将父类对象应用于子类的特征就是多态。例如,创建一个螺丝类,螺丝类有两个属性,即螺丝粗细和螺纹密度。然后创建两个类,即一个长螺丝类和一个短螺丝类,并且它们都继承了螺丝类。这样长螺丝类和短螺丝类不仅具有相同的特征(螺丝粗细相同,且螺纹密度也相同),而且还具有不同的特征(一个长,一个短)。综上,一个螺丝类衍生出不同的子类,子类继承父类特征的同时也具备了自己的特征,并且能实现不同的效果。