面向对象的组合用法
软件重用的重要方式除了继承之外还有另外一种方式,即:组合
组合指的是,在一个类中以另外一个类的对象作为数据属性,称为类的组合
例1
# 人狗大战
class Person:
def __init__(self, name, sex, hp, ad): # 对象属性
self.name = name
self.sex = sex
self.hp = hp
self.ad = ad
self.money = 0 # 设置初始钱为0
def attack(self, d): # 人的攻击方法
d.hp -= self.ad
print('{}攻击了{},{}掉了{}点血'.format(self.name, d.name, d.name, self.ad))
def pay(self): # 充值方法
money = int(input('请输入你要充值的金额:').strip())
self.money += money
print('你的余额是:{}'.format(self.money))
def wear(self, weapon):
# 武器
if self.money >= weapon.price:
# 武器类的对象作为人类对象的一个属性
self.weapon = weapon # 组合 给人装备了武器
self.money -= weapon.price
print('购买成功,你已经顺利装备了{}'.format(weapon.name))
else:
print('余额不足,请充值!')
def attack_with_weapon(self, dog):
if 'weapon' in self.__dict__:
self.weapon.skill(dog)
else:
print('请先装备武器')
class Dog:
def __init__(self, name, kind, hp, ad): # 名字,品种,血量,攻击
self.name = name
self.kind = kind
self.hp = hp
self.ad = ad
def bite(self, p):
p.hp -= self.ad
print('{}咬了{}一口,{}掉了{}点血'.format(self.name, p.name, p.name, self.ad))
class Weapon:
def __init__(self, name, price, ad, level): # 名字,价格,攻击,品级
self.name = name
self.price = price
self.level = level
self.ad = ad * self.level
def skill(self, dog):
dog.hp -= self.ad
print('{}受到了{}的伤害,{}掉了{}点血'.format(dog.name, self.name, dog.name, self.ad))
# 武器 是人的一个属性
# 武器也是一个类
# 攻击力 价格 名字 品级
# 技能:方法
# 武器 装备
# 实例化 人,狗,武器
sam = Person('奥特曼', '不详', 1, 1)
teddy = Dog('笨笨', 'teddy', 50, 20)
futou = Weapon('斧头', 1000, 100, 1)
lst = ['攻击', '充值', '装备武器', '使用武器攻击']
while True:
for index, value in enumerate(lst, 1):
print(index, value)
num = int(input('请选择操作序号 >>>'))
if num == 1:
sam.attack(teddy)
elif num == 2:
sam.pay()
elif num == 3:
print('装备前余额{}'.format(sam.money))
sam.wear(futou)
print('装备后余额{}'.format(sam.money))
elif num == 4:
sam.attack_with_weapon(teddy)
else:
print('无效的序号')
执行效果
圆环是由两个圆组成的,圆环的面积是外面圆的面积减去内部圆的面积。圆环的周长是内部圆的周长加上外部圆的周长
这个时候,我们就首先实现一个圆形类,计算一个圆的周长和面积,然后在"环形类"中组合圆形的实例作为自己的属性来用
例2,上面类组合的例子不是很懂?来看个简单的吧
from math import pi # 导入模块math中的pi
class Circle: # 圆形面积类
# 关联:圆形的面积公式不会变
def __init__(self, r):
self.r = r
def cal_area(self): # 计算圆面积
return pi * self.r ** 2
def cal_perimter(self): # 计算圆周长
return pi * self.r * 2
class Ring: # 圆环
def __init__(self, out_r, in_r): # out_r外圆半径,in_r内圆半径
# 组合(即和上面圆形求面积的类关联起来) 实例化一个类Circle,把外圆半径传进去
self.out_circle = Circle(out_r)
# 组合(即和上面圆形求面积的类关联起来) 实例化一个类Circle,把内圆半径传进去
self.in_circle = Circle(in_r)
def area(self):
# 圆环面积 = 外圆面积-内圆面积
return self.out_circle.cal_area() - self.in_circle.cal_area()
def perimter(self):
# 圆环周长 = 外圆周长+内圆周长
return self.out_circle.cal_perimter() + self.in_circle.cal_perimter()
# 实例化Ring类,并传入大圆半径和小圆半径
st = Ring(6, 2)
# 打印圆环面积
print('圆环的面积为:{}'.format(st.area()))
# 打印圆环周长
print('圆环的周长为:{}'.format(st.perimter()))
执行顺序
执行结果
圆环的面积为:100.53096491487338
圆环的周长为:50.26548245743669
例3,还是类组合!
class BirthDate: # 生日类
def __init__(self, year, month, day):
self.year = year # 年
self.month = month # 月
self.day = day # 日
class Couse: # 课程类
def __init__(self, name, price, period):
self.name = name # 课程名
self.price = price # 课程价格
self.period = period # 课程班级(期)
class Teacher: # 老师类
def __init__(self, name, gender, birth, course):
self.name = name # 姓名
self.gender = gender # 性别
self.birth = birth # 生日
self.course = course # 课程
def teach(self):
print('teaching')
# 实例化 组合:一个类的对象作为另一个类对象的属性
pl = Teacher('sam', 'boy',BirthDate('2008', '12', '12'),Couse('Python3', '20000', '11期'))
print(pl.birth.year, pl.birth.month, pl.birth.day)
print(pl.course.name, pl.course.price, pl.course.period)
执行结果
2008 12 12
Python3 20000 11期
类组合什么时候需要用到?
当类之间有显著不同,并且较小的类是较大的类所需要的组件时,用组合比较好
初始面向对象小结
面向对象的思想
不关注程序执行的过程
关心的是一个程序中的角色以及角色与角色之间的关系
在python中一切皆对象
实例化的过程
创建一个对象
__init__给对象添加一些属性,对象默认的名字self
将self所指向的内存空间返回给实例化它的地方
使用这个对象可以找到两个东西
1 对象所在的内存空间中存储的属性
2 类对象指针所指类中的所有方法和静态属性
对象找名字的时候:先找自己内存空间中的,再找类的
对象没有权利修改类中的静态变量和方法,如果修改了,那么就存在自己的对象空间里面
类名:实例化对象,调用静态属性,执行方法
交互:对象可以作为参数传递给类中的方法
组合:对象可以作为一个对象的属性
组合和交互在python中随处可见
例1
class B:pass
class A:
def func(self, aaa):
print(aaa)
a = A()
b = B()
a.func(b) # b = B()
执行结果,打印类(B)的内存地址
例2
class B:pass
class A:
def func(self, aaa): # aaa = '666'
self.aaa = aaa
a = A() # 实例化
b = B() # 实例化
a.func('666') #执行func并传入一个字符串'666'
print(a.aaa.startswith('6')) #判断'666'是否以'6'开头
执行结果
True
面向对象的三大特性
继承
多态
封装
什么是继承
继承是一种创建新类的方式,在python中,新建的类可以继承一个或多个父类,父类又可称为基类或超类,新建的类称为派生类或子类
python中类的继承分为:单继承和多继承
class ParentClass1: # 定义父类
pass
class ParentClass2: # 定义父类
pass
class SubClass1(ParentClass1): # 单继承,基类是ParentClass1,派生类是SubClass
pass
class SubClass2(ParentClass1, ParentClass2): # python支持多继承,用逗号分隔开多个继承的类
pass
# 查看继承
# __base__只查看从左到右继承的第一个子类, __bases__则是查看所有继承的父类
print(SubClass1.__bases__)
print(SubClass2.__bases__)
# 提示:如果没有指定基类,python的类会默认继承object类,object是所有python类的基类
print(ParentClass1.__bases__)
print(ParentClass2.__bases__)
执行结果
(
(
继承与抽象(先抽象再继承)
抽象即抽取类似或者说比较像的部分
抽象分为两个层次
抽象最主要的作用是划分类别(可以隔离关注点,降低复杂度)
继承:是基于抽象的结果,通过编程语言去实现它,肯定是先经历抽象这个过程,才能通过继承的方式去表达出抽象的结构
抽象只是分析和设计的过程中,一个动作或者说一种技巧,通过抽象可以得到类
例子:
"""
示例 1
猫: 猫喵喵,吃喝拉撒
狗: 狗汪汪,吃喝拉撒
如果要分别为猫和狗创建一个类,那么就需要为猫狗定义它们的动作
代码如下
"""
class 猫:
def 喵喵(self):
return '喵喵'
def 吃(self):
return '吃'
def 喝(self):
return '喝'
def 拉(self):
return '拉'
def 撒(self):
return '撒'
class 狗:
def 汪汪(self):
return '汪汪'
def 吃(self):
return '吃'
def 喝(self):
return '喝'
def 拉(self):
return '拉'
def 撒(self):
return '撒'
# 上述代码不难看出,吃喝拉撒是猫和狗都具有的属性,而却分别为猫狗都定义了一次,代码重复性太高
如果用继承的思想,代码应该这样写,如下
class 动物:
def 吃(self):
return '吃'
def 喝(self):
return '喝'
def 拉(self):
return '拉'
def 撒(self):
return '撒'
# 在类后面括号中写入另外一个类名,表示当前类继承另外一个类
class 猫(动物):
def 喵喵(self):
return '喵喵'
# 在类后面括号中写入另外一个类名,表示当前类继承另外一个类
class 狗(动物):
def 汪汪(self):
return '汪汪'
上面代码只是一个继承的思想(严格来说,最好不要用中文)实际代码如下:
class Animal:
def eat(self):
print('{}吃'.format(self.name))
def drink(self):
print('{}喝'.format(self.name))
def shit(self):
print('{}拉'.format(self.name))
def pee(self):
print('{}撒'.format(self.name))
class Cat(Animal):
def __init__(self, name):
self.name = name
self.breed = '猫'
def cry(self):
print('喵喵')
class Dog(Animal):
def __init__(self, name):
self.name = name
self.breed = '狗'
def cry(self):
print('{}汪汪'.format(self.name))
cl = Cat('小白家的小黑猫')
cl.eat()
c2 = Cat('小黑的小白猫')
c2.drink()
d1 = Dog('胖子家的二哈')
d1.eat()
d2 = Dog('瘦子家的藏獒')
d2.cry()
执行结果
小白家的小黑猫吃
小黑的小白猫喝
胖子家的二哈吃
瘦子家的藏獒汪汪
在开发程序的过程中,如果我们定义了一个类A,然后又想新建立另外一个类B,但是类B的大部分内容与类A的相同,不可能从头开始写一个类B,这就用到了类的继承的概念
通过继承的方式新建类B,让B继承A,B会'遗传'A的所有属性(数据属性和函数属性)
例1
class Animal:
'''
人和狗都是动物,所以创造一个Animal基类
'''
def __init__(self, name, aggressivity, life_value):
self.name = name # 人和狗都有自己的昵称
self.aggressivity = aggressivity # 人和狗都有自己的攻击力
self.life_value = life_value # 人和狗都有自己的生命值
def eat(self):
print('{} is eating'.format(self.name))
class Dog(Animal):
pass
class Person(Animal):
pass
egg = Person('egon', 10, 1000)
ha2 = Dog('二愣子', 50, 1000)
egg.eat()
ha2.eat()
执行结果
egon is eating
二愣子 is eating
提示:用已经有的类建立一个新的类,这样就重用了已经有的软件中的一部分设置,大大降低了编程工作量,这就是常说的软件重用,不仅可以重用自己的类,也可以继承别人的,比如标准库,来定制新的数据类型,这样就大大缩短了软件开发周期,对大型软件开发来说,意义重大
派生
当然子类也可以添加自己新的属性或者在自己这里重新定义这种属性(不会影响到父类),需要注意的是,一旦重新定义了自己的属性且与父类重名,那么调用新增的属性时,就以自己为准了。
class Animal:
'''
人和狗都是动物,所以创造一个Animal基类
'''
def __init__(self, name, aggressivity, life_value):
self.name = name # 人和狗都有自己的昵称;
self.aggressivity = aggressivity # 人和狗都有自己的攻击力;
self.life_value = life_value # 人和狗都有自己的生命值;
def eat(self):
print('%s is eating' % self.name)
class Dog(Animal):
'''
狗类,继承Animal类
'''
def bite(self, people):
'''
派生:狗有咬人的技能
:param people:
'''
people.life_value -= self.aggressivity
class Person(Animal):
'''
人类,继承Animal
'''
def attack(self, dog):
'''
派生:人有攻击的技能
:param dog:
'''
dog.life_value -= self.aggressivity
egg = Person('egon',10,1000)
ha2 = Dog('二愣子',50,1000)
print(ha2.life_value)
egg.attack(ha2)
print(ha2.life_value)
执行结果
1000
990
像ha2.life_value之类的属性引用,会先从实例中找life_value然后去类中找,然后再去父类中找,直到最顶级的父类
经典面试题
class Parent:
def __init__(self):
self.func()
def func(self):
print('in parent func')
class Son(Parent):
def func(self):
print('in son func')
class Att(Son):
def func(self): #如果子类中的方法与父类中的方法名字相同,执行自己的(子类的)
print('in att func')
s = Att()
执行结果
in att func