(十七)類與類之間的關係
⼀. 類與類之間的依賴關係
⼤千世界, 萬物之間皆有規則和規律。我們的類和物件是對⼤千世界中的所有事物進⾏歸類, 那事物之間存在著相對應的關係。 類與類之間也同樣如此,在⾯向物件的世界中 類與類中存在以下關係:
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. 使⽤物件呼叫變數或者方法