高级类(一)

前一章介绍了快速定义和使用类的基本知识。类是引用类型,可以用来支持传统的面向对象编程。

类引入了继承、重写、多态性和组合。这些额外的特性需要考虑特殊的初始化、类层次结构和理解内存中的类生命周期。

这一章将向你介绍Swift中更复杂的类,并帮助你理解如何创建更复杂的类。

继承

在前一章中,你看到了一个Grade结构和两个类的例子:Person 和Student。

struct Grade {
  var letter: Character
  var points: Double
  var credits: Double
}
class Person {
  var firstName: String
  var lastName: String
  init(firstName: String, lastName: String) {
    self.firstName = firstName
    self.lastName = lastName
  }
}

class Student {
  var firstName: String
  var lastName: String
  var grades: [Grade] = []
  init(firstName: String, lastName: String) {
    self.firstName = firstName
    self.lastName = lastName
  }
  func recordGrade(_ grade: Grade) {
    grades.append(grade)
  }
}

不难看出,人与学生之间存在着很多相同的地方。你也许已经发现一个学生就是一个人!

这个简单的案例展示了类继承背后的思想。就像在现实世界中,你可以把学生看作是一个人,你可以用下面的方法来代替原来的学生类,表示代码中的相同关系:

class Student: Person {
  var grades: [Grade] = []
  func recordGrade(_ grade: Grade) {
    grades.append(grade)
  }
}

在这个修改的示例中,Student类现在继承自Person,在Student类名后加 : ,后面是Student继承的类,在这个例子中是Person。

通过继承,学生自动获取Person类中声明的属性和方法。在代码中,说一个学生是一个人是正确的

使用更少的代码,你就可以创建具有Person所有属性和方法的Student对象:

let john = Person(firstName: "Johnny", lastName: "Appleseed")
let jane = Student(firstName: "Jane", lastName: "Appleseed")
john.firstName // "John"
jane.firstName // "Jane"

另外,只有Student对象具有Student类中定义的所有的属性和方法。

let history = Grade(letter: "B", points: 9.0, credits: 3.0)
jane.recordGrade(history)
// john.recordGrade(history) // john is not a student!

从另一个类继承的类被称为子类或派生类,它继承的类被称为超类或基类。

子类化的规则相当简单:

•一个Swift类可以从另一个类继承,这个概念被称为single继承。

•对子类的深度没有限制,这意味着你可以从一个类中子类化。如下所示这也是一个子类:

class BandMember: Student {
  var minimumPracticeTime = 2
}
class OboePlayer: BandMember {
  // This is an example of an override, which we’ll cover soon.
  override var minimumPracticeTime: Int {
get {
      return super.minimumPracticeTime * 2
    }
set {
      super.minimumPracticeTime = newValue / 2
    }
} }

一串子类被称为类层次结构。在这个例子中,层次结构将是OboePlayer -> BandMember -> Student -> Person。类层次结构类似于家族树。由于这个类比,超类也被称为其子类的父类。

多态性

Student/Person 关系展示了一种被称为多态性的计算机科学概念。简言之,多态性是一种编程语言的能力,它可以根据上下文对对象进行不同的处理。

一个OboePlayer当然是一个OboePlayer,但它也是一个人。因为它源于Person,所以你可以在任何使用Person对象的地方使用OboePlayer对象。

这个例子演示了如何将一个OboePlayer视为一个人:

func phonebookName(_ person: Person) -> String {
  return "\(person.lastName), \(person.firstName)"
}
let person = Person(firstName: "Johnny", lastName: "Appleseed")
let oboePlayer = OboePlayer(firstName: "Jane",
                            lastName: "Appleseed")
phonebookName(person) // Appleseed, Johnny
phonebookName(oboePlayer) // Appleseed, Jane

因为OboePlayer来自Person,所以它是函数phonebookName(_:)的有效输入。更重要的是,这个函数不知道传入的对象不是普通的人。它只知道OboePlayer元素是在Person基类中定义的。

基于类继承提供的多态性特征,Swift以不同的上下文对oboePlayer指向的对象进行处理。当你有不同的类层次结构时,并想操作公共类型或基类的代码的时候,这可能对你有用。

层次结构运行时检查

现在你正在使用多态性进行编码,你可能会发现变量后面的特定类型可能会有所不同。例如,你可以定义一个变量hallMonitor作为学生实例:

var hallMonitor = Student(firstName: "Jill",
                          lastName: "Bananapeel")

但是如果hallMonitor是一个更派生的类型,比如OboePlayer呢?

hallMonitor = oboePlayer

因为hallMonitor是作为一个学生定义的,所以编译器不允许你尝试调用属性或方法来获得更派生的类型。

幸运的是,Swift提供了将属性或变量作为另一种类型的操作符:

• as :转换到编译时已知的特定类型,例如转换为超类型。
• as? :可选的向下转换(到子类型)。如果向下转换失败,表达式的结果将为nil。
• as! :强制转换。如果向下转换失败,程序将崩溃。使用这个很少,只有当你确信转换永远不会失败。

这些可以在不同的上下文中使用,可以将hallMonitor转换为一个BandMember或oboePlayer,这些不是太派生的类。

oboePlayer as Student
(oboePlayer as Student).minimumPracticeTime // ERROR: No longer a band
member!
hallMonitor as? BandMember
(hallMonitor as? BandMember)?.minimumPracticeTime // 4 (optional)
hallMonitor as! BandMember // Careful! Failure would lead to a runtime
crash.
(hallMonitor as! BandMember).minimumPracticeTime // 4 (force unwrapped)

在使用let 和 guard语句中,可选的向下转换 as? 是很有用的。

if let hallMonitor = hallMonitor as? BandMember {
  print("This hall monitor is a band member and practices at least \(hallMonitor.minimumPracticeTime)
hours per week.")
}

你可能想知道,在什么情况下,你将使用as操作符?

假设你有两个具有相同名称和参数名称的函数,用于两个不同的参数类型:

func afterClassActivity(for student: Student) -> String {
  return "Goes home!"
}
func afterClassActivity(for student: BandMember) -> String {
  return "Goes to practice!"
}

如果你将oboePlayer传递到afterClassActivity(for:),那么哪一个方法会被调用?答案就在Swift的调度规则中,在本例中,它将选择使用更具体的版本。

如果你把oboePlayer转化为Student,就会调用参数是Student类型的方法:

afterClassActivity(for: oboePlayer) // Goes to practice! 
afterClassActivity(for: oboePlayer as Student) // Goes home!

继承,方法和重写

子类接收其超类中定义的所有属性和方法,加上子类定义的任何其他属性和方法。在这个意义上,子类是补充剂。例如,你已经看到Student类可以添加额外的属性和方法来处理学生的成绩。这些属性和方法不能用于任何Person类实例,但是它们可以用于Student子类。

除了创建自己的方法之外,子类还可以覆盖(重写)超类中定义的方法。假设学生运动员在比赛中失败三次或三次以上没有资格参加田径项目。这就意味着你需要知道不及格的成绩。

class StudentAthlete: Student {
  var failedClasses: [Grade] = []
  override func recordGrade(_ grade: Grade) {
    super.recordGrade(grade)
    if grade.letter == "F" {
      failedClasses.append(grade)
    }
}
  var isEligible: Bool {
    return failedClasses.count < 3
  }
}

在这个例子中,StudentAthlete类覆盖了recordGrade(_:),这样它就可以跟踪学生失败的任何课程。StudentAthlete有自己的计算属性,也就是,使用这些信息来确定运动员的资格。

当重写方法时,在方法声明之前使用override关键字。

如果你的子类有一个相同的方法声明作为它的超类,但是你省略了override关键字,Swift会指出一个构建错误:


QQ20180524-143203@2x.png

这样就可以很清楚地知道一个方法是否覆盖了现有的方法。

super

你可能还注意到覆盖方法中的super. recordgrade (grade)。super关键字与self类似,只是它将调用最近的实现的超类中的方法。在StudentAthlete的recordGrade(_:)的例子中,调用super.recordGrade(grade)将执行在Student类中定义的方法。

记住,继承如何让你定义具有姓氏和姓氏属性的人,并避免在子类中重复这些属性?同样,能够调用超类方法意味着你可以编写代码来记录学生的成绩,然后根据需要在子类中向上调用。

虽然它并不总是必需的,但是在Swift中重写方法时调用super是很重要的。super 调用是在grade数组中记录分数本身的内容,因为StudentAthlete(学生运动员)的行为并不是重复的。调用super也是避免StudentAthlete(学生运动员)和Student(学生)重复代码的一种方式。

何时调用super

你可能注意到的,当你调用super时,它对你覆盖的方法有重要的影响。

假设你在StudentAthlete(学生运动员)类中替换了overriden recordGrade(_:)方法,该方法使用以下版本,每次记录一个grade时重新计算failedClasses:

override func recordGrade(_ grade: Grade) {
  var newFailedClasses: [Grade] = []
  for grade in grades {
    if grade.letter == "F" {
      newFailedClasses.append(grade)
    }
  }
  failedClasses = newFailedClasses
  super.recordGrade(grade)
}

这个版本的recordGrade(_:)在grades数组中查找失败的成绩。既然你在最后调用super,如果是新的grade.letter等于F,会在此添加grade到failedClasses,代码就不能正确地更新failedClasses。

虽然这不是一个硬性规则,但通常最好是在重写时首先调用方法的super。这样,超类就不会体验到它的子类引入时产生的副作用,子类也不需要知道超类的实现细节。

防止继承

有时,你希望禁用某类的子类。Swift为你提供了final关键字,以保证类永远不会得到子类:

final class FinalStudent: Person {}
class FinalStudentAthlete: FinalStudent {} // Build error!

通过 final 标记FinalStudent类,你告诉编译器阻止任何类从FinalStudent继承。这可以提醒你——或者你的团队中的其他人!-这个类不是为有子类而设计的。

此外,如果你允许类具有子类,但是想保护单个方法不被重写或者覆盖,你可以将单个方法标记为final:

class AnotherStudent: Person {
  final func recordGrade(_ grade: Grade) {}
}
class AnotherStudentAthlete: AnotherStudent {
  override func recordGrade(_ grade: Grade) {} // Build error!
}

在开始给任何新类标记final时,都有一些好处。这告诉编译器它不需要查找任何更多的子类,它可以缩短编译时间;很明确的告诉你,不需要子类化的类标记了final。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,869评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,716评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,223评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,047评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,089评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,839评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,516评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,410评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,920评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,052评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,179评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,868评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,522评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,070评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,186评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,487评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,162评论 2 356

推荐阅读更多精彩内容

  • Scala与Java的关系 Scala与Java的关系是非常紧密的!! 因为Scala是基于Java虚拟机,也就是...
    灯火gg阅读 3,448评论 1 24
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,648评论 18 399
  • 盆景缘阅读 271评论 0 0
  • 今天休班,没有出门,正好赶上下雨,这场雨从中午一直下到晚上,而且下的还不小。 晚上闲来无事,正好有时间看看电影,正...
    小城的卡夫卡阅读 264评论 0 0
  • 东隅,日出;桑榆,日落。珍惜朝阳,无悔告别夕阳。 回望过去,每一次的第一天都被赋予了不同寻常的意义。 幼儿园的第一...
    木行上阅读 472评论 0 0