高級(jí)類(lèi)(三)

何時(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)引用。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 未經(jīng)允許不得轉(zhuǎn)載。轉(zhuǎn)載請(qǐng)事電郵hphuahua@sina.com或簡(jiǎn)書(shū)私信 2013年2月22日 今天我做了一個(gè)拉...
    晨寧Chinadoll閱讀 546評(píng)論 4 0
  • 不要試圖用戰(zhàn)術(shù)上的勤奮,掩飾戰(zhàn)略上了懶惰。 每天都在學(xué)習(xí)和提升著一些自己喜歡的語(yǔ)言和技能,對(duì)事物充滿(mǎn)著好奇心。 但...
    貓幾何閱讀 300評(píng)論 0 0
  • 家風(fēng)是一條河 兒時(shí)的生活總有抹不去的記憶 那時(shí)父母不用對(duì)我們講節(jié)約 因?yàn)槲覀兩萸缶褪敲恐苣艹陨弦活D肉 盼望著什么時(shí)...
    山風(fēng)木風(fēng)閱讀 558評(píng)論 0 0

友情鏈接更多精彩內(nèi)容