面向对象练习
本篇是通过简单的案例来强化面向对象的基础,让大家更深刻地了解类和对象以及面向对象编程的特点,主要以两个现实中常见的事务抽象化进行类的封装。
封装
- 封装 是面向对象编程的一大特点
- 面向对象编程的第一步 —— 将 属性 和 方法 封装 到一个抽象的类中
- 外界使用类创建对象,然后让对象调用方法
- 对象方法的细节都被封装在类的内部
案例一 小明爱跑步
需求:邻居家有一个孩子叫小明,今年15岁,体重45公斤。他特别喜欢跑步,每次跑步都会减重0.3公斤,但是他正处于长身体的年纪,一顿饭会增重0.5公斤,今天上午他还没吃早餐就出去跑步锻炼了,中午累了才回来休息吃饭,由于上午运动太多,他下午在家学习,晚饭过去两个小时他又去跑步锻炼,就这样过了丰富的一天。请用面向对象的方式计算小明今天过后的体重是多少。
代码如下:
class Person:
def __init__(self, name, age, weight):
self.name = name
self.age = age
self.weight = weight
def eat(self):
self.weight += 0.5
def run(self):
self.weight -= 0.3
xiaoming = Person('xiaoming', 15, 45)
xiaoming.run()
xiaoming.eat()
xiaoming.eat()
xiaoming.run()
print(f'小明当前体重为:{xiaoming.weight:.2f}')
我们首先定义了一个Person类,类里面封装了姓名、年龄以及体重属性和eat、run方法,两个方法里面实现了体重的增加与减少。随后我们快速创建了一个对象(即小明),通过一一对照小明的行为调用对应的方法进行体重的计算,最后我们得到了小明今天过后的体重。
结果为小明当前体重为:45.40
现在,我们已经成功实现了小明的体重变化,现在有了新的情况,小明在外结识了一个朋友小美,她今年16岁,体重40公斤,她每天吃一顿长0.5公斤,运动一次减重0.3公斤,她每天在吃完早饭之后开始做体操,然后中午和晚饭都是准时的,由于她在学习钢琴,所以每天只运动一次,请用面向对象的方式实现她的体重变化。
其实对于这个问题,我们只需要在小明的基础上在增加一个对象小美即可,对象和方法不会互相影响。
class Person:
def __init__(self, name, age, weight):
self.name = name
self.age = age
self.weight = weight
def eat(self):
self.weight += 0.5
def run(self):
self.weight -= 0.3
xiaoming = Person('xiaoming', 15, 45)
xiaoming.run()
xiaoming.eat()
xiaoming.eat()
xiaoming.run()
print(f'小明当前体重为:{xiaoming.weight:.2f}')
xiaomei = Person('xiaomei', 16, 40)
xiaomei.eat()
xiaomei.run()
print(f'小美当前体重为:{xiaomei.weight:.2f}')
结果如下:
小明当前体重为:45.40
小美当前体重为:40.20
在上述过程中,我们调用对象的方法对属性值进行修改,这是因为在类的内部是可以直接访问对象的属性。这样我们就通过一个类完成了不同人物之间的属性变化,两个对象调用的都是一个类里面封装的方法,且他们的属性互不干扰。
案例二 摆放家具
由于小明特别喜欢运动,他的父母打算换一个居住环境,新增一个小的健身房。他家的新房是中户型、总面积120平米,新房子没有任何家具,所以要将老房的家具搬过来并且添置新家具。主要家具及占地面积如下:
- 席梦思(bed) 占地
4平米 - 衣柜(wardrobe) 占地
2平米 - 餐桌(table) 占地
1.5平米
现在将三种家具添置到新房中,请用面向对象的方法计算剩余面积,并且输出房子的户型、总面积、剩余面积、家具名称列表等。
对于该问题,如果我们只用一个类是比较难的,因为不仅有房子对象,还有家具,他们的属性都相差较大,所以我们定义两个类——房子(House)以及家具(Furniture),由于房子类再添加家具过程会使用到家具类,所以家具类应该先开发。
家具类(Furniture):
class Furniture:
def __init__(self, name, area):
self.name = name
self.area = area
def __str__(self):
return f'[{self.name}]占地{self.area:.2f}平米'
# 创建家具
bed = Furniture("席梦思", 4)
wardrobe = Furniture("衣柜", 2)
table = Furniture("餐桌", 1.5)
print(bed)
print(wardrobe)
print(table)
我们通过家具类创建了三个家具,并且输出相关信息如下:
[席梦思]占地4.00平米
[衣柜]占地2.00平米
[餐桌]占地1.50平米
接下来,我们进行房子类的设计,房子类较为复杂,首先他的属性有些是需要初始化的有些是其他传入的,首先我们可以确定的是房子的户型和面积是最开始便具有的属性,所以应该放入初始化方法的形参当中,而剩余面积最开始和房屋面积一致,添加家具后才会发生变化,家具名称列表初始为空列表,家具放入后才会变化,所以初始化方法中形参传入户型和面积,在该方法中定义剩余面积和家具列表的初始值。
class House:
def __init__(self, house_type, area):
self.house_type = house_type
self.area = area
self.remaining_area = area
self.furniture_list = []
def __str__(self):
return (f'户型:{self.area},总面积:{self.area:.2f},剩余面积:'
f'{self.remaining_area:.2f},家具列表:{self.furniture_list}')
def add_furniture(self, furniture):
print(f'要添加的家具为{furniture}')
new_house = House("中户型", 120)
print(new_house)
输出信息如下:
户型:120,总面积:120.00,剩余面积:120.00,家具列表:[]
现在我们完成了房子类的基本构建,但是这样并不能添加家具和对面积的计算,需要在add_furniture方法中完善这两个功能,首先,我们要了解使用房子类创建的对象在添加家具对象时所添加的对象信息,我们首先将家具添加的方法写为如下形式:
...
def add_furniture(self, furniture):
self.furniture_list.append(furniture)
print(f'要添加的家具为{furniture}')
new_house = House("中户型", 120)
# 添加家具
new_house.add_furniture(bed)
new_house.add_furniture(wardrobe)
new_house.add_furniture(table)
print(new_house)
输出的结果如下:
[席梦思]占地4.00平米
[衣柜]占地2.00平米
[餐桌]占地1.50平米
要添加的家具为[席梦思]占地4.00平米
要添加的家具为[衣柜]占地2.00平米
要添加的家具为[餐桌]占地1.50平米
户型:120,总面积:120.00,剩余面积:120.00,家具列表:[<__main__.Furniture object at 0x000001D74774FF40>, <__main__.Furniture object at 0x000001D74774FD90>, <__main__.Furniture object at 0x000001D74774FD30>]
可以看到,在外面添加家具列表的方法中添加了家具后,列表内却是得到四个对象信息,说明家具是成功添加到房子当中的,我们只需要指定属性就可以查看对应信息:
完整房子类:
class House:
def __init__(self, house_type, area):
self.house_type = house_type
self.area = area
self.remaining_area = area
self.furniture_list = []
def __str__(self):
return (f'户型:{self.area},总面积:{self.area:.2f},剩余面积:'
f'{self.remaining_area:.2f},家具列表:{self.furniture_list}')
def add_furniture(self, furniture):
print(f'当前剩余面积{self.remaining_area}')
print(f'要添加的家具为{furniture}')
if furniture.area <= self.remaining_area:
self.remaining_area -= furniture.area
self.furniture_list.append(furniture.name)
print(f"{furniture.name}添加成功")
print('_'*20)
else:
print(f"{furniture}面积太大,无法放置")
完整家具拜访:
class Furniture:
def __init__(self, name, area):
self.name = name
self.area = area
def __str__(self):
return f'[{self.name}]占地{self.area:.2f}平米'
# 创建家具
bed = Furniture("席梦思", 114) //数据设置为方便程序的if分支判断
wardrobe = Furniture("衣柜", 7)
table = Furniture("餐桌", 1.5)
class House:
def __init__(self, house_type, area):
self.house_type = house_type
self.area = area
self.remaining_area = area
self.furniture_list = []
def __str__(self):
return (f'户型:{self.area},总面积:{self.area:.2f},剩余面积:'
f'{self.remaining_area:.2f},家具列表:{self.furniture_list}')
def add_furniture(self, furniture):
print(f'当前剩余面积{self.remaining_area}')
print(f'要添加的家具为{furniture}')
if furniture.area <= self.remaining_area:
self.remaining_area -= furniture.area
self.furniture_list.append(furniture.name)
print(f"{furniture.name}添加成功")
print('_'*20)
else:
print(f"{furniture}面积太大,无法放置")
new_house = House("中户型", 120)
# 添加家具
new_house.add_furniture(bed)
new_house.add_furniture(wardrobe)
new_house.add_furniture(table)
print(new_house)
家具摆放完成之后得到如下结果:
当前剩余面积120
要添加的家具为[席梦思]占地114.00平米
席梦思添加成功
____________________
当前剩余面积6
要添加的家具为[衣柜]占地7.00平米
[衣柜]占地7.00平米面积太大,无法放置
当前剩余面积6
要添加的家具为[餐桌]占地1.50平米
餐桌添加成功
____________________
户型:120,总面积:120.00,剩余面积:4.50,家具列表:['席梦思', '餐桌']
可以看到,家具已经被添置到新房中,且面积等信息页完成了更新。在这个过程中,我们可以看到在房子类的家具添加方法中有name属性和area属性,请注意,这两个属性都不是房子类中的属性,而是家具类中的属性(虽然在房子类中也有area属性,但是它们是不同的)。既然不是房子类的属性,那它为什么在该类中的方法出现,且程序依然可以执行呢?由于python程序的执行是自上而下顺序执行的,该程序首先用家具类创建了三个家具对象,然后又利用房子类创建了一个新房,然后新房对象顺序添加了三个家具,这三个家具对象便被传入到了新房对象的家具添加方法内,所以该方法内部的name和area属性是家具的,而不是房子的。
案例三 士兵突击
多年以后,小明因为经常锻炼,体质特别好,参军成为一名优秀的士兵。他现在有一把AK47突击步枪,枪能够发射子弹,小明可以用枪开火,枪装弹夹后增加子弹数量,开火射击之后减少子弹。
对于这个问题,我们需要定义两个类,士兵(Soldiers)和枪(Gun),而士兵小明拥有name属性以及gun属性,而gun可以通过枪类创建对象来作为士兵小明的属性。这也是本案例涉及到的新知识点:一个对象的属性可以是另一个类创建的对象。既然枪式被士兵使用的,那么我们应该先定义枪类,在定义士兵类。
枪类:
class Gun:
def __init__(self, model):
# 1. 枪的型号
self.model = model
# 2. 子弹数量
self.bullet = 0
def add_bullet(self, count):
self.bullet += count
def show_bullet(self):
# 判断子弹数量
if self.bullet <= 0:
print(f'{self.model}没有子弹,请增加子弹')
return
# 发射子弹
self.bullet -= 1
# 发射信息
print(f'[{self.model}]突突突...')
士兵类:
在设计士兵类时,我们要考虑到一个外界因素,即新兵是不会有枪的,需要经过一段时间之后才会训练该科目,所以在定义属性省可以设为None。
class Solder:
def __init__(self, name):
# 1.新兵名字
self.name = name
# 2.新兵没有枪
self.gun = None
...
# 2.创建士兵对象
xiaoming = Solder("Xiaoming")
# 给新兵授枪
xiaoming.gun = ak47
print(xiaoming.gun.model)
现在,我们已经完成了枪类和士兵类的创建,但是士兵类还需要完善一下开火方法。
class Solder:
...
# 开火方法
def gun_fire(self):
# 1.判断士兵是否有枪
if self.gun is None:
print(f'{self.name}还没有授枪')
return
# 2.检查枪支子弹量
if self.gun.bullet < 50:
print(f'{self.name}当前弹药量为{self.gun.bullet}')
count = 50 - self.gun.bullet
# 完整弹药量为50
self.gun.add_bullet(count)
print(f'[{self.name}]弹药补充完整,现在拥有弹药{self.gun.bullet}')
# 发起冲锋
print(f'[{self.name}]:兄弟们冲啊!')
# 射击
self.gun.show_bullet()
至此,我们就完成了士兵突击全部方法,以下是全部实现的代码:
class Gun:
def __init__(self, model):
# 1. 枪的型号
self.model = model
# 2. 子弹数量
self.bullet = 0
def add_bullet(self, count):
self.bullet += count
def show_bullet(self):
# 判断子弹数量
if self.bullet <= 0:
print(f'{self.model}没有子弹,请增加子弹')
return
# 发射子弹
self.bullet -= 1
# 发射信息
print(f'[{self.model}]突突突...')
class Solder:
def __init__(self, name):
# 1.新兵名字
self.name = name
# 2.新兵没有枪
self.gun = None
# 开火方法
def gun_fire(self):
# 1.判断士兵是否有枪
if self.gun is None:
print(f'{self.name}还没有授枪')
return
# 2.检查枪支子弹量
if self.gun.bullet < 50:
print(f'{self.name}当前弹药量为{self.gun.bullet}')
count = 50 - self.gun.bullet
self.gun.add_bullet(count)
print(f'[{self.name}]弹药补充完整,现在拥有弹药{self.gun.bullet}')
# 发起冲锋
print(f'[{self.name}]:兄弟们冲啊!')
# 射击
self.gun.show_bullet()
# 1.创建抢对象
ak47 = Gun("AK47")
ak47.add_bullet(46)
# 2.创建士兵对象
xiaoming = Solder("Xiaoming")
# 给新兵授枪
xiaoming.gun = ak47
xiaoming.gun_fire()
print(xiaoming.gun.bullet)
输出结果如下:
Xiaoming当前弹药量为46
[Xiaoming]弹药补充完整,现在拥有弹药50
[Xiaoming]:兄弟们冲啊!
[AK47]突突突...
49