目錄
一. 類的多繼承
二. 類的封裝
三. 類的多態
四. 類與對象綜合練習:校園管理系統
一. 類的多繼承
在(2)第四節中我們介紹了什么是類的繼承,在子類的括號里面寫入要繼承的父類名。上一節我們只在括號內寫了一個父類名,但其實寫入多個也是可以的,這說明它同時繼承了多個父類,這就是多繼承。給出一段代碼示例如下:
class Shenxian:def fly(self):print("神仙在飛...")class Monkey:def eat_peach(self):print("猴子在偷吃仙桃...")class Sunhouzi(Shenxian, Monkey):def fangendou(self):print("孫悟空在翻跟斗...")S = Sunhouzi()
S.eat_peach()
這里Sunhouzi就同時繼承了Shenxian和Monkey類,孫猴子也具有Shenxian和Monkey的方法。這段代碼也印證了,如果子類沒有的方法,就要去父類里面尋找同名方法。在多繼承的條件下,如果多個父類都有同名方法,它首先去尋找哪個父類下的同名方法呢?
class Shenxian:def fly(self):print("神仙在飛...")def fight(self):print("神仙在打架...") class Monkey:def eat_peach(self):print("猴子在偷吃仙桃...")def fight(self):print("猴子在打架...")class Sunhouzi(Monkey, Shenxian):def fangendou(self):print("孫悟空在翻跟斗...")S = Sunhouzi()
S.fight() # 猴子在打架...
可見它是按照括號內的順序調用的,從左往右發現某父類有就直接調用該父類下的方法。
現在我們考慮更復雜的情況:把Monkey類下的fight下方法刪除,同時寫一個Organism類,讓Shenxian和Monkey類都繼承Organism類。此時相當于多重繼承:Sunhouzi類繼承了Shenxian和Monkey類,Shenxian和Monkey類又繼承Organism類:
class Organism:def fight(self):print("群魔亂舞,各種生物都在打架...")class Shenxian(Organism):def fly(self):print("神仙在飛...")def fight(self):print("神仙在打架...") class Monkey(Organism):def eat_peach(self):print("猴子在偷吃仙桃...")class Sunhouzi(Shenxian, Monkey):def fangendou(self):print("孫悟空在翻跟斗...")S = Sunhouzi()
S.fight() # 神仙在打架...
此時我們再讓孫猴子打架,得到的輸出是“神仙在打架...”
表面上來看,這類似于圖的廣度優先遍歷(關于什么是圖的廣度優先遍歷和深度優先遍歷,可以參考數據結構博客:20.圖的遍歷-CSDN博客),然而實際上多重繼承時確定子類繼承哪一個父類方法用的是C3算法。C3算法實現了三種重要特性:
- 保持繼承拓撲圖的一致性。
- 保證局部優先原則(比如A繼承C,C繼承B,那么A讀取父類方法,應該優先使用C的方法而不是B的方法)。
- 保證單調性原則(即子類不改變父類的方法搜索順序)。
我們并不去詳細介紹C3算法(因為沒有開發人員這么去干),只給出一段代碼說明多重繼承時既不滿足廣度優先遍歷也不滿足深度優先遍歷:
class A:def test(self):print("from A")class B(A):passclass B2:def test(self):print("from B2")class C(A):def test(self):print("from C")class C2:def test(self):print("from C2")class D(B, B2):passclass E(C, C2):passclass F(D, E):passf1 = F()
f1.test()
# from C
用C3方法解析的順序即方法解析順序(Method Resolution Order,MRO),我們可以用mro()查看,這樣我們不需要懂C3算法也可以知道它的方法解析順序:
print(F.mro())
# [<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class '__main__.B2'>, <class '__main__.C2'>, <class 'object'>]
二. 類的封裝
封裝可以被認為是一個保護屏障,防止該類的代碼和數據被外部類定義的代碼隨機訪問要訪問該類的代碼和數據,必須通過嚴格的接口控制。封裝最主要的功能在于我們能修改自己的實現代碼,而不用修改那些調用我們代碼的程序片段。適當的封裝可以讓程式碼更容易理解與維護,也加強了代碼數據的安全性。封裝的優點如下:
- 良好的封裝能夠減少耦合。
- 類內部的結構可以自由修改。
- 可以對成員變量進行更精確的控制。
- 隱藏信息,實現細節。
封裝的原則是:
- 將不需要對外提供的內容都隱藏起來;
- 把屬性都隱藏,提供公共方法對其訪問。
以下是一個沒有封裝的例子,我們可以不打槍就使得人的血量發生減少:
class Person:def __init__(self, name, sex):self.name = nameself.sex = sexself.life_val = 100a = Person("zhangsan", "M")
a.life_val = 50
print(a.life_val) # 50
為了解決這個問題我們需要對變量進行私有化,即加__,此時在外部是無法訪問私有屬性life_val的:
class Person:def __init__(self, name, sex):self.name = name # 實例變量,成員變量self.sex = sexself.__life_val = 100 # 私有變量,私有屬性,加__使得變量私有化a = Person("zhangsan", "M")
print(a.__life_val)
# AttributeError: 'Person' object has no attribute '__life_val'
但是私有屬性在類的內部是可以被訪問的,如:
class Person:def __init__(self, name, sex):self.name = nameself.sex = sexself.__life_val = 100 def get_life_val(self):print("生命值還有:", self.__life_val)def got_attack(self):self.__life_val -= 20print("[%s]受到了攻擊,掉了20滴血,現在生命值是[%s]" % (self.name, self.__life_val))a = Person("zhangsan", "M")
a.get_life_val() # 生命值還有: 100
a.got_attack() # [zhangsan]受到了攻擊,掉了20滴血,現在生命值是[80]
a.get_life_val() # 生命值還有: 80
如此我們就可以讓life_val只讀,而不能對其進行外部修改。
同理,我們也可以對方法進行封裝,只在類的內部進行調用:
class Person:def __init__(self, name, sex):self.name = nameself.sex = sexself.__life_val = 100 def __breath(self):print(self.name,"在呼吸...")a = Person("zhangsan", "M")
a.__breath() # AttributeError: 'Person' object has no attribute '__breath'
如果非要在外部進行訪問,則需要寫:實例對象._類名+方法名,相當于解封裝:
class Person:def __init__(self, name, sex):self.name = name # 實例變量,成員變量self.sex = sexself.__life_val = 100 # 私有變量,私有屬性,加__使得變量私有化a = Person("zhangsan", "M")
print(a._Person__life_val) # 100
# 如果直接寫print(a.__life_val),則會報AttributeError
三. 類的多態
有時一個對象會有多種表現形式,比如網站頁面有個button按鈕,這個button的設計可以不一樣(單選框、多選框、圓角的點擊按鈕、直角的點擊按鈕等),盡管長的不一樣,但它們都有一個共同調用方式,就是onClick()方法。我們直要在頁面上一點擊就會觸發這個方法。點完后有的按鈕會變成選中狀態、有的會提交表單、有的甚至會彈窗。這種多個對象共用同一個接口,又表現的形態不一樣的現象,就叫做多態(Polymmorphism)。
Polymorphilsm is based on the greek words Poly (many) and morphism(forms), 接下來我們通過代碼來演示什么是多態。
(1)通過統一函數接口實現多態
class Dog():def sound(self):print("汪汪汪...")class Cat():def sound(self):print("喵喵喵...")def make_sound(animal):animal.sound()dog = Dog()
cat = Cat()
make_sound(cat) # 喵喵喵...
make_sound(dog) # 汪汪汪...
(2)通過抽象類實現多態(最常用)
假如你開發一個文本編輯器,支持多種文檔類型, 在用戶通過你的編輯器打開文件之前,你也不知道準備要打開的是什么類型的文件,可能是pdf,也可能是word。
假如你為每個文件類型都寫一個類,每個類都通過show()方法來調用打開對應的文檔,為了確保每個類都必須實現show()方法,你可以寫一個抽象類Document:
class Document():def __init__(self, name):self.name = namedef show(self):raise NotImplementedError("Subclass must implement abstract method")class Pdf(Document):def show(self):print("Show Pdf contents!")class Word(Document):def show(self):print("Show Word contents!")pdf = Pdf("1.pdf")
word = Word("2.doc")
obj = [pdf, word]
for o in obj:o.show()
四. 類與對象綜合練習:校園管理系統
設計一個培訓機構管理系統,有總部、分校,有學員、老師、員工,實現具體如下需求:
1.有多個課程,課程要有定價
2.有多個班級,班級跟課程有關聯
3.有多個學生,學生報名班級,交這個班級對應的課程的費用
4.有多個老師,可以分布在不同校區,上不同班級的課
5.有多個員工,可以分布在不同校區在總部可以統計各校區的賬戶余額、員工人數、學員人數
6.學生可以轉校、退學
隨便瞎寫了一個,可能略有出入,如有語法錯誤歡迎指正!
(此部分建議先自己動手寫,如果寫不出來再去看別人的代碼)
class School():def __init__(self, name, address):self.name = nameself.address = addressself.branches = set()self.stuff = set()self.classes = set()def print_address(self):print("學校[%s]的地址是[%s]" % (self.name, self.address))def print_classes(self):print("學校[%s]目前所開班型有:" % self.name)for i in self.classes:print(i.class_name)def print_branches(self):print("學校[%s]目前的分校有:" % self.name)for i in self.branches:print(i.name)def print_stuff(self):print("學校[%s]目前的雇員有:" % self.name)for i in self.stuff:print(i.name)def pay_roll(self):self.count_teacher_num()self.count_stuff_num()money = 0for i in self.stuff:money += i.salaryfor i in self.classes:money += i.teacher.salaryprint("總校應發工資:%s" % money)for i in self.branches:for j in i.stuff: money += j.salaryfor j in i.classes:money += j.teacher.salaryprint("總校分校合計應發工資:%s" % money)def count_teacher_num(self):teacher_num = len(self.classes)for i in self.branches:teacher_num += len(i.classes)print("[%s]有教師數量[%s]"%(self.name, teacher_num))def count_stuff_num(self):stuff_num = len(self.stuff)for i in self.branches:stuff_num += len(i.stuff)print("[%s]有職工數量[%s]"%(self.name, stuff_num))def count_stu_num(self):stu_num = 0for i in self.classes:stu_num += len(i.student)for i in self.branches:for j in i.classes:stu_num += len(j.student)print("[%s]有學生數量[%s]"%(self.name, stu_num))class BranchSchool():def __init__(self, name, address, headquater):self.name = nameself.address = addressself.headquater = headquaterheadquater.branches.add(self)self.classes = set()self.stuff = set()def print_address(self):print("學校[%s]的地址是[%s]" % (self.name, self.address))def print_classes(self):print("學校[%s]目前所開班型有:" % self.name)for i in self.classes:print(i.class_name)def print_stuff(self):print("學校[%s]目前的雇員有:" % self.name)for i in self.stuff:print(i.name)class Kecheng():def __init__(self, class_name, class_id, price, num_period, branchschool):self.class_name = class_nameself.class_id = class_idself.price = priceself.num_period = num_periodself.student = set()self.branchschool = branchschoolbranchschool.classes.add(self)self.teacher = Nonedef print_class_information(self):if self.teacher == None:print("本課程未指定授課教師,請調用教師類方法指定授課教師!")else:print("[%s]課程基本信息:" % self.class_name)print("課序號:[%s]" % self.class_id)print("開課校區:[%s]" % self.branchschool.name)print("授課教師:[%s]" % self.teacher.name)print("本課程學時:[%s]" % self.num_period)def print_student(self):self.print_class_information()print("-------本課程學生名單----------")for i in self.student:print(i.name,i.student_id)class Student():def __init__(self, name, student_id):self.name = nameself.student_id = student_idself.kecheng = Nonedef join_class(self, kecheng):self.kecheng = kechengself.Number_of_hours_attended = 0kecheng.student.add(self)print("學生[%s]選報了[%s]課程,課序號:[%s]" % (self.name, kecheng.class_name, kecheng.class_id))def print_information(self):print("學生[%s], 學號[%s]" % (self.name, self.student_id))if self.kecheng == None:print("該生沒有選報課程!")else:print("該生所上課程[%s], 已上課時數[%s]" % (self.kecheng.class_name, self.Number_of_hours_attended))def shangke(self):self.Number_of_hours_attended += 1print("學生[%s]來上課了,已上課時數[%s]" % (self.name, self.Number_of_hours_attended))def tuixue(self):shengyuxueshi = self.kecheng.num_period - self.Number_of_hours_attendedmoney = shengyuxueshi * self.kecheng.priceprint("學生[%s]退課了,剩余課時數[%s],應退學費[%s]元" % (self.name, shengyuxueshi, money))self.kecheng.student.remove(self)class Teacher():def __init__(self, name, salary, teacher_id):self.name = nameself.salary = salaryself.kecheng = Noneself.teacher_id = teacher_iddef print_information(self):print("教師[%s], 教師工作證號[%s]" % (self.name, self.teacher_id))if self.kecheng == None:print("該老師不執教任何課程!")else:print("該老師所上課程[%s]" % (self.kecheng.name))def join_class(self, kecheng):self.kecheng = kechengkecheng.teacher = selfprint("教師[%s]成為課程[%s]的教師!課序號[%s]" % (self.name, kecheng.class_name, kecheng.class_id))class Stuff():def __init__(self, name, salary, stuff_id):self.name = nameself.salary = salaryself.stuff_id = stuff_idself.school = Nonedef print_information(self):print("雇員[%s], 工作證號[%s]" % (self.name, self.stuff_id))if self.school == None:print("該雇員還沒有分配校區!")else:print("所在校區[%s]" % (self.school.name))def join_school(self, school):self.school = schoolprint("雇員[%s]加入[%s]校區" % (self.name, school.name))school.stuff.add(self)school1 = School("北京總校", "北京市海淀區清華大學")
branchschool1 = BranchSchool("北京分校", "北京站1號", school1)
school2 = School("上海總校", "上海市黃浦區南京東路1號")
branchschool2 = BranchSchool("上海分校", "虹橋火車站", school2)
branchschool3 = BranchSchool("上海第二分校", "松江大學城", school2)
student1 = Student("張三", 4312784)
student2 = Student("李四", 4371289)
student3 = Student("王五", 6578584)
student4 = Student("趙八", 3421554)
student5 = Student("曹一", 1254265)
student6 = Student("操日本", 9432713)
teacher1 = Teacher("趙老憨", 15000, 5478238)
teacher2 = Teacher("李老師", 18000, 8033427)
kecheng1 = Kecheng("數據結構", 548792, 500, 45, school1)
kecheng2 = Kecheng("計算機組成原理", 431287, 500, 45, branchschool1)
stuff1 = Stuff("王瑩", 20000, 412379)
stuff2 = Stuff("趙鵬", 1000, 4127982)
stuff3 = Stuff("李麻瓜", 2000, 5189312)
school2.print_branches()
school1.print_classes()
student1.print_information()
branchschool1.print_classes()
student1.join_class(kecheng1)
student2.join_class(kecheng2)
student3.join_class(kecheng1)
teacher1.join_class(kecheng1)
teacher2.join_class(kecheng2)
kecheng1.print_student()
school1.count_stu_num()
student1.print_information()
student1.shangke()
student1.shangke()
student1.tuixue()
stuff1.join_school(school1)
stuff2.join_school(branchschool1)
stuff3.join_school(school2)
school1.print_stuff()
school1.pay_roll()