第十五章 继承

一个类可以继承另一个类的方法、属性和其它特性。当一个类继承其它类时,继承类叫子类,被继承类叫超类或父类。 在 Swift 中,继承是区分类与其它类型的一个基本特征。

在 Swift 中,类可以调用和访问超类的方法,属性和附属脚本,并且可以重写这些方法,属性和附属脚本来优化或修改它们的行为。Swift 会检查你的重写 义在超类中是否有匹配的定义,以此确保你的重写行为是正确的。

可以为类中继承来的属性添加属性观察器,这样一来,当属性值改变时,类就会被通知到。可以为任何属性添加属性观察器,无论它原本被定义为存储型属性还是计算型属性。

 

定义一个基类

不继承于其它类的类,称之为基类。   

 

注意:Swift 中的类并不是从一个通用的基类继承而来。如果你不为你定义的类指定一个超 类的话,这个类就自动成为基类。 

下面的例子定义了一个叫 Vehicle 的基类。这个基类声明了两个对所有车辆都通用的属性。这些属性在 description 方法中使用,这个方法 返回一个 String 类型的,对车辆特征的描述: 

<此处添加代码 P237>

 

Vehicle 类定义了构造器(initializer)来设置属性的值。构造器会在构造过程一节中详细介 绍,这里我们做一下简单介绍,以便于讲解子类中继承来的属性如何被修改。   

构造器用于创建某个类型的一个新实例。尽管构造器并不是方法,但在语法上,两者很相似。 构造器的工作是准备新实例以供使用,并确保实例中的所有属性都拥有有效的初始化值。   

构造器的最简单形式就像一个没有参数的实例方法,使用 init 关键字:

<此处添加代码 P237>

 

如果要创建一个 Vehicle 类的新实例,使用构造器语法调用上面的初始化器,即类名后面跟 一个空的小括号: 

<此处添加代码 P237>

 

这个 Vehicle 类的构造器为任意的一辆车设置一些初始化属性值(numberOfWheels = 0 和 maxPassengers = 1)。   

Vehicle 类定义了车辆的共同特性,但这个类本身并没太大用处。为了使它更为实用,你需要进一步细化它来描述更具体的车辆。 

 

子类生成(Subclassing) 

子类生成(Subclassing)指的是在一个已有类的基础上创建一个新的类。子类继承超类的 特性,并且可以优化或改变它。你还可以为子类添加新的特性。   为了指明某个类的超类,将超类名写在子类名的后面,用冒号分隔: 

<此处添加代码 P238>

 

下一个例子,定义一个更具体的车辆类叫 Bicycle。这个新类是在 Vehicle 类的基础上创建 起来。因此你需要将 Vehicle 类放在 Bicycle 类后面,用冒号分隔。 

 

我们可以将这读作:  

 “定义一个新的类叫 Bicycle,它继承了 Vehicle 的特性”; 

<此处添加代码 P239>

 

Bicycle 是 Vehicle 的子类,Vehicle 是 Bicycle 的超类。新的 Bicycle 类自动获得 Vehicle 类 的特性,比如 maxPassengers 和 numberOfWheels 属性。你可以在子类中定制这些特性, 或添加新的特性来更好地描述 Bicycle 类。   

Bicycle 类定义了一个构造器来设置它定制的特性(自行车只有 2 个轮子)。Bicycle 的构造 器调用了它父类 Vehicle 的构造器 super.init(),以此确保在 Bicycle 类试图修改那些继承来 的属性前 Vehicle 类已经初始化过它们了。   

注意:不像 Objective-C,在 Swift 中,初始化器默认是不继承的,见初始化器的继承与重 写   Vehicle 类中 maxPassengers 的默认值对自行车来说已经是正确的,因此在 Bicycle 的构造 器中并没有改变它。而 numberOfWheels 原来的值对自行车来说是不正确的,因此在初始 化器中将它更改为 2。   

Bicycle 不仅可以继承 Vehicle 的属性,还可以继承它的方法。如果你创建了一个 Bicycle 类 的实例,你就可以调用它继承来的 description 方法,并且可以看到,它输出的属性值已经 发生了变化:

<此处添加代码 P240>

子类还可以继续被其它类继承: 

<此处添加代码 P240>

上面的例子创建了 Bicycle 的一个子类:双人自行车(tandem)。 Tandem 从 Bicycle 继承了 两个属性,而这两个属性是 Bicycle 从 Vehicle 继承而来的。Tandem 并不修改轮子的数量, 因为它仍是一辆自行车,有 2 个轮子。但它需要修改 maxPassengers 的值,因为双人自 行车可以坐两个人。   

注意:子类只允许修改从超类继承来的变量属性,而不能修改继承来的常量属性。   

创建一个 Tandem 类的实例,打印它的描述,即可看到它的属性已被更新: 

<此处添加代码 P240>

 

注意,Tandem 类也继承了 description 方法。一个类的实例方法会被这个类的所有子类继 承。 

 

重写(Overriding) 

子类可以为继承来的实例方法(instance method),类方法(class method),实例属性 (instance property),或附属脚本(subscript)提供自己定制的实现(implementation)。 我们把这种行为叫重写(overriding)。   

如果要重写某个特性,你需要在重写定义的前面加上 override 关键字。这么做,你就表明了你是想提供一个重写版本,而非错误地提供了一个相同的定义。意外的重写行为可能会导致 不可预知的错误,任何缺少 override 关键字的重写都会在编译时被诊断为错误。   

override 关键字会提醒 Swift 编译器去检查该类的超类(或其中一个父类)是否有匹配重写 版本的声明。这个检查可以确保你的重写定义是正确的。 

访问超类的方法,属性及附属脚本 

当你在子类中重写超类的方法,属性或附属脚本时,有时在你的重写版本中使用已经存在的超类实现会得到意想不到的好处。

比如,你可以优化已有实现的行为,或在一个继承来的变量中存储一 个修改过的值。   

在合适的地方,你可以通过使用 super 前缀来访问超类版本的方法,属性或附属脚本:   

在方法 someMethod 的重写实现中,可以通过 super.someMethod()来调用超类版本的 someMethod 方法。 

在属性 someProperty 的 getter 或 setter 的重写实现中,可以通过 super.someProperty 来访问超类版本的 someProperty 属性。 

在附属脚本的重写实现中,可以通过 super[someIndex]来访问超类版本中的相同附属脚本。 

 

重写方法 

在子类中,你可以重写继承来的实例方法或类方法,提供一个定制或替代的方法实现。   

下面的例子定义了 Vehicle 的一个新的子类,叫 Car,它重写了从 Vehicle 类继承来的 description 方法: 

<此处添加代码 P242>

Car 声明了一个新的存储型属性 speed,它是 Double 类型的,默认值是 0.0,表示“时速是 0 英里”。Car 有自己的初始化器,它将乘客的最大数量设为 5,轮子数量设为 4。   

Car 重写了继承来的 description 方法,它的声明与 Vehicle 中的 description 方法一致,声 明前面加上了 override 关键字。   

Car 中的 description 方法并非完全自定义,而是通过 super.description 使用了超类 Vehicle 中的 description 方法,然后再追加一些额外的信息,比如汽车的当前速度。   

如果你创建一个 Car 的新实例,并打印 description 方法的输出,你就会发现描述信息已经 发生了改变:

<此处添加代码 P243>

重写属性 

你可以重写继承来的实例属性或类属性,提供自己定制的 getter 和 setter,或添加属性观察 器使重写的属性观察属性值什么时候发生改变。 

 

重写属性的 Getters和 Setters 

你可以提供定制的 getter(或 setter)来重写任意继承来的属性,无论继承来的属性是存储型的还是计算型的属性。子类并不知道继承来的属性是存储型的还是计算型的,它只知道继承来的属性会有一个名字和类型。你在重写一个属性时,必需将它的名字和类型都写出来。 这样才能使编译器去检查你重写的属性是与超类中同名同类型的属性相匹配的。   

你可以将一个继承来的只读属性重写为一个读写属性,只需要你在重写版本的属性里提供 getter 和 setter 即可。但是,你不可以将一个继承来的读写属性重写为一个只读属性。  

 注意:如果你在重写属性中提供了 setter,那么你也一定要提供 getter。如果你不想在重写 版本中的 getter 里修改继承来的属性值,你可以直接返回 super.someProperty 来返回继承 来的值。正如下面的 SpeedLimitedCar 的例子所示。 

以下的例子定义了一个新类,叫 SpeedLimitedCar,它是 Car 的子类。类 SpeedLimitedCar 表示安装了限速装置的车,它的最高速度只能达到 40mph。你可以通过重写继承来的 speed 属性来实现这个速度限制: 

<此处添加代码 P244>

当你设置一个 SpeedLimitedCar 实例的 speed 属性时,属性 setter 的实现会去检查新值与 限制值 40mph 的大小,它会将超类的 speed 设置为 newValue 和 40.0 中较小的那个。这 两个值哪个较小由 min 函数决定,它是 Swift 标准库中的一个全局函数。min 函数接收两个 或更多的数,返回其中最小的那个。   

如果你尝试将 SpeedLimitedCar 实例的 speed 属性设置为一个大于 40mph 的数,然后打 印 description 函数的输出,你会发现速度被限制在 40mph:

<此处添加代码 P245>

重写属性观察器(Property Observer) 

你可以在属性重写中为一个继承来的属性添加属性观察器。这样一来,当继承来的属性值发生改变时,你就会被通知到,无论那个属性原本是如何实现的。关于属性观察器的更多内容, 请看属性观察器。

注意:你不可以为继承来的常量存储型属性或继承来的只读计算型属性添加属性观察器。这 些属性的值是不可以被设置的,所以,为它们提供 willSet 或 didSet 实现是不恰当。此外还 要注意,你不可以同时提供重写的 setter 和重写的属性观察器。如果你想观察属性值的变 化,并且你已经为那个属性提供了定制的 setter,那么你在 setter 中就可以观察到任何值 变化了。   

 

下面的例子定义了一个新类叫 AutomaticCar,它是 Car 的子类。AutomaticCar 表示自动挡 汽车,它可以根据当前的速度自动选择合适的挡位。AutomaticCar 也提供了 定制的 description 方法,可以输出当前挡位。   

<此处添加代码 P246>

 

当你设置 AutomaticCar 的 speed 属性,属性的 didSet 观察器就会自动地设置 gear 属性, 为新的速度选择一个合适的挡位。具体来说就是,属性观察器将新的速度值除以 10,然后 向下取得最接近的整数值,最后加 1 来得到档位 gear 的值。例如,速度为 10.0 时,挡位为 1;速度为 35.0 时,挡位为 4: 

<此处添加代码 P246>

防止重写 

你可以通过把方法,属性或附属脚本标记为 final 来防止它们被重写,只需要在声明关键字 前加上@final 特性即可。(例如:@final var, @final func, @final class func, 以及 @final subscript)

如果你重写了 final 方法,属性或附属脚本,在编译时会报错。在扩展中,你添加到类里的 方法,属性或附属脚本也可以在扩展的定义里标记为 final。   你可以通过在关键字 class 前添加@final 特性(@final class)来将整个类标记为 final 的, 这样的类是不可被继承的,否则会报编译错误。

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

推荐阅读更多精彩内容

  • 123.继承 一个类可以从另外一个类继承方法,属性和其他特征。当一个类继承另外一个类时, 继承类叫子类, 被继承的...
    无沣阅读 1,389评论 2 4
  • 官方文档 初始化 Initialization是为准备使用类,结构体或者枚举实例的一个过程。这个过程涉及了在实例里...
    hrscy阅读 1,135评论 0 1
  • 本章将会介绍 存储属性的初始赋值自定义构造过程默认构造器值类型的构造器代理类的继承和构造过程可失败构造器必要构造器...
    寒桥阅读 769评论 0 0
  • #野菊#20161113每天一篇看图写作 今天没有矫情,也没有含蓄,就是喜欢。 如泥土的原貌,藤叶的凋零,它就那么...
    牛牛的老四阅读 123评论 0 0
  • 已过零点, 外面下着大雨, 今天二伯头七。 一辈子, 说长很长, 说短太短。 恍恍惚惚, 说没就没。 挂在嘴边的“...
    飘飘陈阅读 203评论 0 0