⼀. 类与类之间的依赖关系
⼤千世界, 万物之间皆有规则和规律。我们的类和对象是对⼤千世界中的所有事物进⾏归类, 那事物之间存在着相对应的关系。 类与类之间也同样如此,在⾯向对象的世界中 类与类中存在以下关系:
1. 依赖关系
2. 关联关系
3. 组合关系
4. 聚合关系
5. 继承关系
6. 实现关系
先来看紧密程度最低的⼀个----依赖关系。假设要玩游戏,那么使用什么呢?现在的方式挺多的,手机电脑,PS等,都可以,但是如果没有这些工具,就玩不了这样的游戏,所以玩游戏又得依赖这些工具,但是我可以选择使用哪种工具,一种玩够了,可以换别的工具继续玩。看代码:
class Phone:
def __init__(self):
self.name = '手机'
class Computer:
def __init__(self):
self.name = '电脑'
class PS:
def __init__(self):
self.name = 'PS'
class Person:
def play(self, type):
print(f'使用{type.name}打游戏')
phone = Phone()
computer = Computer()
ps = PS()
p = Person()
p.play(phone)
p.play(computer)
p.play(ps)
可以看到,人玩游戏时,是可以随便更换工具的,所以这种依赖关系很不稳定,也就是紧密程度很低。
⼆. 关联关系.组合关系, 聚合关系
其实这三个在代码上写法是⼀样的,但是从含义上是不⼀样的。
1. 关联关系: 两种事物必须是互相关联的,但是在某些特殊情况下是可以更改和更换的。
2. 聚合关系:属于关联关系中的⼀种特例,侧重点是xxx和xxx聚合成xxx。各⾃有各⾃的生命周期,比如电脑,电脑⾥有CPU, 硬盘, 内存等等。电脑挂了,CPU还是好的,还是完整的个体。
3. 组合关系:属于关联关系中的⼀种特例,写法上差不多,组合关系比聚合还要紧密,比如⼈的⼤脑, ⼼脏, 各个器官。这些器官组合成⼀个⼈,这时⼈如果挂了,其他的东⻄也跟着挂了。
⾸先我们看关联关系: 这个最简单,也是最常⽤的⼀种关系。比如,⼤家都有男女朋友,男⼈关联着女朋友,女⼈关联着男朋友,这种关系可以是互相的,也可以是单⽅⾯的。
class Boy:
def __init__(self, name, girlFriend=None):
self.name = name
self.girlFriend = girlFriend
def have_a_dinner(self):
if self.girlFriend:
print("%s 和 %s⼀起去吃晚餐" % (self.name, self.girlFriend.name))
else:
print("单身狗. 吃什么饭")
class Girl:
def __init__(self, name):
self.name = name
b = Boy("jerry")
b.have_a_dinner()
# 突然⽜B了. 找到⼥朋友了
g = Girl("如花")
b.girlFriend = g # 有⼥朋友了
b.have_a_dinner()
gg = Girl("李⼩花")
bb = Boy("tom", gg) # 娃娃亲. 出⽣就有⼥朋友. 服不服
bb.have_a_dinner() # 多么幸福的⼀家
# 突然.bb失恋了. 娃娃亲不跟他好了
bb.girlFriend = None
bb.have_a_dinner() # ⼜单身了
注意:此时Boy和Girl两个类之间就是关联关系,两个类的对象紧密练习着。其中⼀个没有了,另⼀个就孤单的不得了,关联关系, 其实就是我需要你,你也属于我。这就是关联关系。像这样的关系有很多很多, 比如,学校和老师之间的关系。
School --- 学校
Teacher--- 老师
老师必然属于⼀个学校,换句话说,每个老师肯定有⼀个指定的⼯作机构, 就是学校,那老师的属性中必然关联着学校。
class School:
def __init__(self, name, address):
self.name = name
self.address = address
class Teacher:
def __init__(self, name, school=None):
self.name = name
self.school = school
s1 = School("北京大学", "地址111111")
s2 = School("清华大学", "地址222222")
s3 = School("哈佛大学", "地址333333")
t1 = Teacher("Tom", s1)
t2 = Teacher("Jerry", s1)
t3 = Teacher("Tony", s2)
t4 = Teacher("Peter", s3)
# 找到Peter所在的校区地址
print(t4.school.address)
想想,这样的关系如果反过来,⼀个老师可以选⼀个学校任职。那反过来,⼀个学校有多少老师呢? ⼀堆吧? 这样的关系如何来描述呢?
class School:
def __init__(self, name, address):
self.name = name
self.address = address
self.t_list = [] # 每个学校都应该有⼀个装⼀堆⽼师的列表
def add_teacher(self, teacher):
self.t_list.append(teacher)
class Teacher:
def __init__(self, name, school=None):
self.name = name
self.school = school
s1 = School("北京大学", "地址111111")
s2 = School("清华大学", "地址222222")
s3 = School("哈佛大学", "地址333333")
t1 = Teacher("Tom", s1)
t2 = Teacher("Jerry", s1)
t3 = Teacher("Tony", s2)
t4 = Teacher("Peter", s3)
s1.add_teacher(t1)
s1.add_teacher(t2)
s1.add_teacher(t3)
# 查看北京大学有哪些⽼师
for t in s1.t_list:
print(t.name)
好了,这就是关联关系。 当我们在逻辑上出现了,我需要你,你还得属于我。这种逻辑就是关联关系,那注意,这种关系的紧密程度比上⾯的依赖关系要紧密的多。为什么呢? 老师没有学校,就不能教书了,学校没有了老师,就没有老师教学生了。两者是互相需要的,学校需要老师,而且这个老师也是属于这个学校的。开始的依赖关系中,我玩游戏,不用手机,还可以使用其他的电脑或者PS等。
⾄于组合关系和聚合关系,其实代码上的差别不⼤,都是把另⼀个类的对象作为这个类的属性来传递和保存,只是在含义上会有些许的不同⽽已。
三. 继承关系.
在⾯向对象的世界中存在着继承关系,我们现实中也存在着这样的关系,我们说过。 x是⼀种y,那x就可以继承y,这是理解层⾯上的。如果上升到代码层⾯,我们可以这样认为⼦类在不影响⽗类的程序运⾏的基础上对⽗类进⾏的扩充和扩展。这⾥,我们可以把⽗类被称为超类或者基类,⼦类被称为派⽣类。
⾸先, 类名和对象默认是可以作为字典的key的。
class Foo:
def __init__(self):
pass
def method(self):
pass
# __hash__ = None
print(hash(Foo))
print(hash(Foo()))
既然可以hash. 那就是说字典的key可以是对象或者类。虽然显⽰的有点⼉诡异. 但是是可以⽤的。
dic = {}
dic[Foo] = 123
dic[Foo()] = 456
print(dic) # {<class '__main__.Foo'>: 123, <__main__.Foo object at 0x103491550>: 456}
接下来,我们来继续研究继承上的相关内容。在此主要研究⼀下self,记住不管⽅法之间如何进⾏调⽤,类与类之间是何关系,默认的self都是访问这个⽅法的对象。
分析一下上面的代码:1号线表示初始化时,把值=123给了num;2号线表示obj调用func1(),但是在类Foo中没有这个方法,所以程序会去Foo的父类Base中查找,OK,找到了,同时把obj传递给了self。在func1()里打印完num的值后,又调用了func2(),谁调用的这个func2()呢?当然是self,这里的self,又是func1()被调用时,传递进来的obj,也就是Foo的对象,所以,要找func2(),也得去Foo中找,这是就近原则,也就是3号线的方向,虽然这样看起来是跑远了,但是Python就是这样规定的。很完美找到了,所以直接打印就好了。看看下面代码,自己玩玩吧。
class Base:
def __init__(self, num):
self.num = num
def func1(self):
print(self.num)
self.func2()
def func2(self):
print(111, self.num)
class Foo(Base):
def func2(self):
print(222, self.num)
lst = [Base(1), Base(2), Foo(3)]
for obj in lst:
obj.func1() # 那笔来吧. 算好了,可以跑一遍,看看结果。
四. 类中的特殊成员
什么是特殊成员呢? __init_()就是⼀个特殊的成员,说⽩了带双下划线的那些, 这些⽅法在特殊的场景的时候会被⾃动的执⾏。 比如:
1. 类名() 会⾃动执⾏__init__()
2. 对象() 会⾃动执⾏__call__()
3. 对象[key] 会⾃动执⾏__getitem__()
4. 对象[key] = value 会⾃动执⾏__setitemm__()
5. del 对象[key] 会⾃动执⾏ __delitem__()
6. 对象+对象 会⾃动执⾏ __add__()
7. with 对象 as 变量 会⾃动执⾏__enter__ 和__exit__
8. 打印对象的时候 会⾃动执⾏ __str__
9. ⼲掉可哈希 __hash__ == None 对象就不可哈希了.
创建对象的真正步骤:
⾸先, 在执⾏类名()的时候,系统会⾃动先执⾏__new__()来开辟内存,此时新开辟出来的内存区域是空的。紧随其后, 系统⾃动调⽤__init__()来完成对象的初始化⼯作,按照时间轴来算。
1. 加载类
2. 开辟内存(__new__)
3. 初始化(__init__)
4. 使⽤对象调用变量或者方法