何時(shí)以及為什么要子類(lèi)化。
本章介紹了類(lèi)的繼承,以及子類(lèi)化相關(guān)的眾多編程技術(shù)。
但是你可能會(huì)問(wèn),“什么時(shí)候我應(yīng)該子類(lèi)化?”
對(duì)于這個(gè)問(wèn)題,很少有正確或錯(cuò)誤的答案。辯證的看點(diǎn)這個(gè)問(wèn)題可以幫助你為任何特殊情況做出最佳決策。
以Student學(xué)生和StudentAthlete學(xué)生運(yùn)動(dòng)員類(lèi)為例,你可以簡(jiǎn)單地把StudentAthlete學(xué)生運(yùn)動(dòng)員的所有特征都放在Student學(xué)生身上:
class Student: Person {
var grades: [Grade]
var sports: [Sport]
// original code
}
這可以解決所有需要的用例。一個(gè)不做運(yùn)動(dòng)的學(xué)生只會(huì)有一個(gè)空的sports數(shù)組,你可以避免一些額外的復(fù)雜的子類(lèi)化。
單一職責(zé)
在軟件開(kāi)發(fā)中,單一職責(zé)是說(shuō),聲明的任何類(lèi)都應(yīng)該有一個(gè)單獨(dú)職責(zé)。在學(xué)生和學(xué)生運(yùn)動(dòng)員中,學(xué)生有學(xué)生的責(zé)任,運(yùn)動(dòng)員有運(yùn)動(dòng)員的責(zé)任,學(xué)生的責(zé)任不應(yīng)該封裝到運(yùn)動(dòng)員里,運(yùn)動(dòng)員的也不應(yīng)該封裝到學(xué)生里。
強(qiáng)類(lèi)型
使用Swift的類(lèi)型系統(tǒng),子類(lèi)化創(chuàng)建了一個(gè)附加類(lèi)型。你可以基于學(xué)生運(yùn)動(dòng)員而不是普通學(xué)生的對(duì)象聲明屬性或行為:
class Team {
var players: [StudentAthlete] = []
var isEligible: Bool {
for player in players {
if !player.isEligible {
return false
}
}
return true
}
}
一個(gè)隊(duì)有運(yùn)動(dòng)員,他們是學(xué)生運(yùn)動(dòng)員。如果你試圖將一個(gè)普通的學(xué)生對(duì)象添加到players數(shù)組中,類(lèi)型系統(tǒng)將不允許。這很有用,因?yàn)榫幾g器可以幫助你執(zhí)行系統(tǒng)的邏輯和需求。
共享基類(lèi)
你可以通過(guò)具有互斥行為的類(lèi),多次子類(lèi)化一個(gè)共享基類(lèi):
// A button that can be pressed.
class Button {
func press() {}
}
// An image that can be rendered on a button
class Image {}
// A button that is composed entirely of an image.
class ImageButton: Button {
var image: Image
init(image: Image) {
self.image = image
}
}
// A button that renders as text.
class TextButton: Button {
var text: String
init(text: String) {
self.text = text
}
}
在本例中,你可以想象許多Button子類(lèi)只共享按下的事件。因此當(dāng)按下按鈕時(shí),Button子類(lèi)必須實(shí)現(xiàn)自己的行為。ImageButton和TextButton類(lèi)有完全不同的機(jī)制來(lái)呈現(xiàn)按鈕的外觀。
你可以在這里看到,在Button類(lèi)中存儲(chǔ)圖像和文本——更不用說(shuō)可能出現(xiàn)的任何其他類(lèi)型的按鈕,還會(huì)有其他的樣式。因此按鈕與媒體行為有關(guān),只有處理按鈕的實(shí)際外觀的子類(lèi)才是有意義的。
可擴(kuò)展性
有時(shí),如果你要擴(kuò)展你的代碼不擁有的行為,那么你就必須進(jìn)行子類(lèi)化。在上面的示例中,可能Button是你正在使用的框架的一部分,并且你不可能修改或擴(kuò)展源代碼以滿(mǎn)足你的需要。
在這種情況下,子類(lèi)化Button可以添加自定義子類(lèi),并使用這種類(lèi)型按鈕對(duì)象來(lái)使用Button。
同一性
最后,重要的是要理解類(lèi)和類(lèi)層次結(jié)構(gòu)模型的對(duì)象是什么。如果你的目標(biāo)是在類(lèi)型之間共享行為(對(duì)象可以做什么),那么通常你應(yīng)該更喜歡協(xié)議而不是子類(lèi)化。
理解類(lèi)的生命周期
在前一章中,你了解到對(duì)象是在內(nèi)存中創(chuàng)建的,它們存儲(chǔ)在堆中。堆上的對(duì)象不會(huì)被自動(dòng)銷(xiāo)毀,因?yàn)槎阎皇且粋€(gè)巨大的內(nèi)存池。如果沒(méi)有調(diào)用堆棧的實(shí)用程序,就沒(méi)有自動(dòng)的方法來(lái)知道一個(gè)內(nèi)存將不再被使用。
在Swift中,決定何時(shí)清理堆上未使用的對(duì)象的機(jī)制稱(chēng)為引用計(jì)數(shù)。簡(jiǎn)而言之,每個(gè)對(duì)象都有一個(gè)引用計(jì)數(shù),每一個(gè)常量或變量對(duì)該對(duì)象的引用都會(huì)遞增,并且每次刪除引用時(shí)都會(huì)遞減。
注意:在其他書(shū)籍和在線(xiàn)資源中,你可能會(huì)看到引用計(jì)數(shù)被稱(chēng)為“保留計(jì)數(shù)”。他們指的是同一件事!
當(dāng)引用計(jì)數(shù)達(dá)到0時(shí),這意味著該對(duì)象現(xiàn)在被放棄,因?yàn)橄到y(tǒng)中沒(méi)有任何引用。當(dāng)這種情況發(fā)生時(shí),Swift將清理對(duì)象。
這里展示了一個(gè)對(duì)象的引用計(jì)數(shù)的變化。注意,在這個(gè)示例中只創(chuàng)建了一個(gè)實(shí)際對(duì)象;一個(gè)對(duì)象有很多引用。
var someone = Person(firstName: "Johnny", lastName: "Appleseed")
// Person object has a reference count of 1 (someone variable)
var anotherSomeone: Person? = someone
// Reference count 2 (someone, anotherSomeone)
var lotsOfPeople = [someone, someone, anotherSomeone, someone]
// Reference count 6 (someone, anotherSomeone, 4 references in
lotsOfPeople)
anotherSomeone = nil
// Reference count 5 (someone, 4 references in lotsOfPeople)
lotsOfPeople = []
// Reference count 1 (someone)
someone = Person(firstName: "Johnny", lastName: "Appleseed")
// Reference count 0 for the original Person object!
// Variable someone now references a new object
在本例中,你不需要自己做任何工作來(lái)增加或減少對(duì)象的引用計(jì)數(shù)。這是因?yàn)镾wift具有自動(dòng)引用計(jì)數(shù)或ARC的特性。
雖然一些較舊的語(yǔ)言要求你在代碼中增加和減少引用計(jì)數(shù),但是Swift編譯器在編譯時(shí)自動(dòng)添加這些調(diào)用。
注意:如果你使用像C這樣的低級(jí)語(yǔ)言,那么你需要手動(dòng)釋放內(nèi)存。像Java和c#這樣的高級(jí)語(yǔ)言使用了稱(chēng)為垃圾收集的東西。在這種情況下,運(yùn)行時(shí),將在清理不再使用的對(duì)象之前,搜索進(jìn)程引用的對(duì)象。垃圾收集雖然比ARC更強(qiáng)大,但它帶來(lái)的內(nèi)存利用率低和性能成本高,所以蘋(píng)果公司決定不接受移動(dòng)設(shè)備或通用系統(tǒng)語(yǔ)言。
Deinitialization
當(dāng)一個(gè)對(duì)象的引用計(jì)數(shù)達(dá)到0時(shí),Swift將對(duì)象從內(nèi)存中移除,并將其標(biāo)記為空閑內(nèi)存。
deinitializer是一個(gè)特殊的方法,它在對(duì)象的引用計(jì)數(shù)達(dá)到0時(shí)運(yùn)行,但是在Swift將對(duì)象從內(nèi)存中移除之前。
修改類(lèi)Person如下:
class Person {
// original code
deinit {
print("\(firstName) \(lastName) is being removed
from memory!")
}
}
很像init是類(lèi)初始化中的一種特殊方法,deinit是一個(gè)處理初始化的特殊方法。與init不同,deinit不是必需的,并且會(huì)被Swift自動(dòng)調(diào)用。你也不需要覆蓋它或在其中調(diào)用super。Swift將確保調(diào)用每個(gè)類(lèi)的deinitializer。
如果你添加這個(gè)deinitializer,在運(yùn)行前一個(gè)示例后的調(diào)試區(qū)域中,你將看到Johnny Appleseed正在被從內(nèi)存中刪除的消息!
你在deinitializer中所做的事情取決于你。通常,你將使用它來(lái)清理其他資源,將狀態(tài)保存到磁盤(pán),或者在對(duì)象超出范圍時(shí)執(zhí)行你可能需要的任何其他邏輯。
循環(huán)引用和弱引用
由于Swift的類(lèi)依賴(lài)于引用計(jì)數(shù)來(lái)將它們從內(nèi)存中刪除,所以理解retain cycle的概念非常重要。
添加一個(gè)代表同學(xué)的字段—例如,一個(gè)實(shí)驗(yàn)室伙伴—和一個(gè)deinitializer方法,這樣的學(xué)生:
class Student: Person {
var partner: Student?
// original code
deinit {
print("\(firstName) is being deallocated!")
}
}
var alice: Student? = Student(firstName: "Alice",
lastName: "Appleseed")
var bob: Student? = Student(firstName: "Bob",
lastName: "Appleseed")
alice?.partner = bob
bob?.partner = Alice
現(xiàn)在假設(shè)alice和bob都輟學(xué)了:
alice = nil
bob = nil
如果你在你的playground上運(yùn)行這個(gè),你會(huì)注意到,Swift不調(diào)用deinit。這是為什么呢?
Alice和Bob各自有一個(gè)引用,所以引用計(jì)數(shù)永遠(yuǎn)不會(huì)達(dá)到零!更糟糕的是,通過(guò)給alice和bob分配nil,就沒(méi)有對(duì)初始對(duì)象的引用了。這是一個(gè)循環(huán)引用的經(jīng)典案例,它導(dǎo)致了一個(gè)被稱(chēng)為內(nèi)存泄漏的軟件缺陷。
在內(nèi)存泄漏的情況下,即使它的實(shí)際生命周期已經(jīng)結(jié)束,內(nèi)存也不會(huì)釋放出來(lái)。循環(huán)引用是內(nèi)存泄漏最常見(jiàn)的原因。
幸運(yùn)的是,有一種方法,學(xué)生對(duì)象可以引用另一個(gè)學(xué)生而不容易循環(huán)引用,這是通過(guò)使用弱引用。
class Student: Person {
weak var partner: Student?
// original code
}
這個(gè)簡(jiǎn)單的修改將伙伴變量標(biāo)記為弱,這意味著該變量中的引用不會(huì)參與引用計(jì)數(shù)。當(dāng)引用不弱時(shí),它被稱(chēng)為強(qiáng)引用,這是Swift的默認(rèn)值。弱引用必須聲明為可選類(lèi)型,以便當(dāng)它們引用的對(duì)象被釋放時(shí),它會(huì)自動(dòng)變?yōu)閚il。
關(guān)鍵點(diǎn)
?類(lèi)繼承是類(lèi)最重要的特性之一,它支持多態(tài)性。
?Swift類(lèi)使用兩階段初始化作為安全措施,確保在使用所有存儲(chǔ)屬性之前都進(jìn)行了初始化。
?子類(lèi)化是一個(gè)強(qiáng)大的工具,但是知道什么時(shí)候子類(lèi)化是很好的。當(dāng)你想要擴(kuò)展一個(gè)對(duì)象,并且可以從子類(lèi)和超類(lèi)之間的“is-a”關(guān)系中受益,但是要注意繼承的狀態(tài)和深層的類(lèi)層次結(jié)構(gòu)。
?類(lèi)實(shí)例有它們自己的生命周期,它們由它們的引用計(jì)數(shù)控制。
?自動(dòng)引用計(jì)數(shù),或ARC,稱(chēng)為自動(dòng)處理引用計(jì)數(shù),但重要的是要注意循環(huán)引用。