原文:Object Oriented Programming in Swift

经过将事物分化成能够承继和组合的目标,学习面向目标的编程在 Swift 中是如何工作的。

面向目标编程(Object oriented programming)是一种基本的编程范式,假如你想要认真学习 Swift,就必须掌握这种范式。这是因为面向目标编程是你即将运用的大多数结构的核心。将一个问题分化成彼此发送音讯的目标,起初或许看起来很奇怪,但这是一种简化杂乱体系的老练办法,能够追溯到20世纪50年代。

目标简直能够用来模仿任何东西——地图上的坐标,屏幕上的触摸,乃至是银行账户中的利率波动。当你刚刚开端的时分,在你将其扩展到更笼统的概念之前,练习对实践世界中的物理事物进行建模是十分有用的。

在本教程中,你将运用面向目标编程来创立你自己的乐队。在这一过程中,你还会学到许多重要的概念,包括:

  • 封装/Encapsulation
  • 承继/Inheritance
  • 重写与重载/Overriding & Overloading
  • 类型与实例/Types & Instances
  • 组合/Composition
  • 多态性/Polymorphism
  • 拜访操控/Access Control

涉及的内容有许多,所以让咱们开端吧! :]

开端

发动 Xcode 并转到 File.… 输入 Instruments 作为称号,挑选 iOS 作为开发渠道,然后点击 Next。挑选保存 Playground 的方位,然后点击创立。删去编辑器中的一切,以便从头开端。

以面向目标的办法设计事物,通常从一个通用的概念开端,延伸到更详细的类型。你想创立乐器,所以从一个乐器类型开端,然后基于它界说详细的(不是字面意思!)乐器,如钢琴和吉他,是十分合理的。把整个工作幻想成一棵乐器的宗族树,一切的东西都是这样从笼统到详细,从上到下活动的。

Swift 中的面向目标编程

一个子类型和它的父类型之间的联系是一种 is-a 联系。例如,“吉他是一种乐器”。现在你对你所处理的目标有了直观的了解,是时分开端实施了。

特点

在 Playground 的顶部增加以下代码块:


// 1
class Instruments {
    // 2
    let brand: String
    // 3
    init(brand: String) {
        // 4
        self.brand = brand
    }
}

这儿做了相当多的工作,所以让咱们把它剖析一下:

  1. 你用 class 关键字创立 Instrument 基类(base class)。这是乐器类层次结构的根类。它界说了一个蓝图,构成了任何种类的乐器的根底。因为它是一个类型,Instrument 这个姓名被大写了。它不一定要大写,但这是 Swift 中的常规。
  2. 你声明了乐器的存储特点(stored properties),这是一切乐器都有的一个特点。在这儿,咱们用 brand 表明乐器的品牌,你把它用字符串表明。
  3. 你用 init 关键字为该类创立一个初始化器(initializer)。其意图是经过初始化一切存储特点来构造新的乐器实例。
  4. 你将作为参数传入的内容赋值到乐器的存储特点 brand 上。因为特点和参数有相同的姓名,你用self 关键字来区分它们。

你现已完成了一个包括 brand 特点的乐器 clsss,但你还没有给它任何行为。是时分以办法的形式增加一些行为到这个组合中了。

办法

你能够对一个乐器进行调音和演奏,而不管它的详细类型。在 Instrument 类中的初始化器之后增加以下代码:

func tune() -> String {
    fatalError("Implement this method for (brand)")
}

tune() 办法是一个占位符函数,假如你调用它,在运行时就会崩溃。有这样的办法的类被说成是笼统(abstract)类,因为它们不是用来直接运用的。相反,你必须界说一个重写该办法的子类来做一些合理的工作,而不是只调用 fatalError()。稍后会有更多关于重写的内容。

class 中界说的函数(Function)被称为办法(method),因为它们能够拜访特点,例如 Instrument 中的 brand 特点。在一个 class 中安排特点和相关操作是操控杂乱性的一个强壮东西。它乃至有一个美丽的姓名:封装(encapsulation)。类的类型被说成是对数据(如存储特点)和行为(如办法)的封装。

接下来,在你的 Instrument 类之前增加以下代码:

class Music {
    let notes: [String]
    init(notes: [String]) {
        self.notes = notes
    }
    func prepared() -> String {
        return notes.joined(separator: " ")
    }
}

这是一个 Music 类,它封装了一个音符数组,并答应你用 prepare() 办法将其拼接成一个字符串。

tune() 办法之后给 Instrument 类增加以下办法:

func play(_ music: Music) -> String {
    return music.prepared()
}

play(_:) 办法回来一个要播映的 String。你或许想知道为什么你要吃力地创立一个特殊的 Music 类型,而不是直接传递一个音符的字符串数组。这有几个好处。创立 Music 有助于建立一个词汇表,使编译器能够检查你的工作,并为将来的扩展创造一个当地。

接下来,在 Instrument 类中紧接着 play(_:) 增加以下办法:

func perform(_ music: Music) {
    print(tune())
    print(play(music))
}

perform(_:) 办法首要对乐器进行调音,然后一气呵成地演奏所给的音乐。你将两种办法组合在一起,构成完美的交响乐。(双关语十分有意义! :] )

Instrument 类的完成而言,便是这样了。现在是时分增加一些详细的乐器了。

承继

在 Playground 的底部,紧接着 Instrument 类的完成,增加以下类声明:

// 1
class Piano: Instruments {
    // 是否有踏板
    let hasPedals: Bool
    // 2
    static let whitekeys = 52
    static let blackKeys = 36
    // 3
    init(brand: String, hasPedals: Bool = false) {
        self.hasPedals = hasPedals
        // 4
        super.init(brand: brand)
    }
    // 5
    override func tune() -> String {
        return "Piano standard tuning for (brand)."
    }
    override func play(_ music: Music) -> String {
        // 6
        let preparedNotes = super.play(music)
        return "Piano playing (preparedNotes)"
    }
}

下面是工作的经过,一步步来:

  1. 你创立了 Piano 类作为 Instrument 的一个子类。一切存储的特点和办法都被 Piano 子类主动承继并可运用。
  2. 一切的钢琴都有完全相同数量的白键和黑键,不管其品牌如何。它们相应的特点的关联值不会动态改变,所以你把特点标记为静态类型(static),以明确这一点。
  3. 初始化器为它的 hasPedals 参数供给了一个默认值,假如你想的话,能够把它关掉。
  4. 在设置子类存储特点 hasPedals 之后,你运用 super 关键字来调用父类初始化器。父类初始化器负责初始化承继的特点——在本例中是 brand 特点。
  5. 你用 override 关键字覆写了从父类承继的 tune() 办法的完成。这儿供给了一个 tune() 的完成,它不调用 fatalError() ,而是为 Piano 做一些特定的工作。
  6. 你覆写了从父类承继的 play(_:) 办法。在这个办法中,你这次运用了 super 关键字来调用Instrument 的办法,以便取得音乐的预备音符,然后在钢琴上演奏。

因为 Piano 派生自 Instrument,你的代码的用户现已对它有了许多了解。它有一个品牌,能够调音、演奏,乃至能够扮演。

Swift 类运用一种叫做**两步初始化(two-phase-initialization)**的初始化过程,以确保在你运用它们之前一切的特点都被初始化。假如你想了解更多关于初始化的常识,请检查咱们的 Swift 初始化系列教程。

钢琴的调音和演奏是相应的,但你能够用不同的办法演奏。因而,现在是时分把踏板加进去了。

办法覆写

Piano 类中,在重载的 play(_:) 办法之后增加以下办法:

func play(_ music: Music, usingPedals: Bool) -> String {
    let preparedNotes = super.play(music)
    if hasPedals && usingPedals {
        return "Play piano notes (preparedNotes) with pedals."
    } else {
        return "Play piano notes (preparedNotes) without pedals."
    }
}

假如 usePedals 为真,并且钢琴实践上有踏板能够运用,这就重载了 play(_:) 办法来运用踏板。它没有运用 override 关键字,因为它有一个不同的参数列表(parameter list)。Swift 运用参数列表(又称签名)来决议运用哪一个。不过你需求当心重载办法,因为它们有或许形成混乱。例如,perform(_:) 办法总是会调用 play(_:) 办法,而不会调用你专门的play(_:usingPedals:) 办法。

替换 play(_:) 办法,在 Piano 中,用一个新的完成版本来调用你的新踏板:

override func play(_ music: Music) -> String {
    return play(music, usingPedals: hasPedals)
}

Piano 类的完成就到此为止。是时分创立一个真实的钢琴实例了,给它调音,并在上面演奏一些十分酷的音乐。]

实例

Piano 类声明之后,在 Playground 的终究增加以下代码块:

// 1
let piano = Piano(brand: "Yamaha", hasPedals: true)
piano.tune()
// 2
let music = Music(notes: ["C", "G", "F"])
piano.play(music, usingPedals: false)
// 3
piano.play(music)
// 4
Piano.whitekeys
Piano.blackKeys

这便是这儿所发生的工作,一步一步来:

  1. 你创立一个钢琴 piano 作为 Piano 类的一个实例,并调用 tune() 办法对它进行调音。请注意,虽然类型(类)总是大写的,但实例总是小写的。相同,这也是常规。
  2. 你声明一个 Music 类的实例 music,用你的特殊重载办法在钢琴上演奏它,让你不运用踏板就能演奏歌曲。
  3. 你调用 Piano 类的 play(_:) 的版本,假如能够的话,它总是运用踏板。
  4. 键数是钢琴类中的 static 类型的常量值,所以你不需求一个特定的实例来调用它们–你只需运用类名前缀。

现在你现已尝到了钢琴音乐的滋味,你能够在其中参加一些吉他独奏。

中间层笼统基类

在 Playground 的终究增加 Guitar 类的完成:

class Guitar: Instruments {
    let stringGauge: String
    init(brand: String, stringGauge: String = "medium") {
        self.stringGauge = stringGauge
        super.init(brand: brand)
    }
}

这就创立了一个新的 Guitar 类,在 Instrument 基类中增加了字符串表的概念,作为一个文本String。和 Instrument 相同,Guitar 被认为是一个笼统类型,它的 tune()play(_:) 办法需求在子类中被重写。这便是为什么它有时被称为中间层笼统基类(intermediate abstract base class)

你会注意到,没有什么能够阻挠你创立一个笼统类的实例。这是真的,也是 Swift 的一个约束(缺点)。有些言语答应你特别阐明一个类是笼统的,你不能创立它的实例。

Guitar 类就到此为止——你现在能够增加一些十分酷的吉他了! 让咱们开端吧! :]

Concrete Guitars/吉他详细类型

你要创立的第一种类型的吉他是原声吉他。将 AcousticGuitar 类增加到 Playground 的末尾,紧随 Guitar 类之后:

class AcousticGuitar: Guitar {
    static let numberOfStrings = 6
    static let fretCount = 20
    override func tune() -> String {
        return "Tune (brand) acoustic with E A D G B E"
    }
    override func play(_ music: Music) -> String {
        let preparedNotes = super.play(music)
        return "Play folk tune on frets (preparedNotes)."
    }
}

一切的原声吉他都有 6 根弦和 20 个琴格,所以你把相应的特点建模为 static,因为它们与一切原声吉他有关。并且它们是常量,因为它们的值永远不会随时刻改变。这个类自己不增加任何新的存储特点,所以你不需求创立一个初始化器,因为它主动承继了其父类吉他的初始化器。是时分用一个应战来测试一下这个吉他了!

应战:界说一把罗兰牌原声吉他。调音,并演奏。

let acousticGuitar = AcousticGuitar(brand: "Roland", stringGauge: "light")
acousticGuitar.tune()
acousticGuitar.play(music)

是时分制造一些噪音,播映一些响亮的音乐了。你将需求一个扩音器! :]

private

原声吉他很好,但扩音器的吉他更酷。在 Playground 的底部增加放大器类,让派对开端:

// 1
class Amplifier {
    // 2
    private var _voumme: Int
    // 3
    private(set) var isOn: Bool
    init() {
        isOn = false
        _voumme = 0
    }
    // 4
    func plugIn() {
        isOn = true
    }
    func unplug() {
        isOn = false
    }
    // 5
    var volume: Int {
        // 6
        get {
            return isOn ? _voumme : 0
        }
        // 7
        set {
            _voumme = min(max(newValue, 0), 10)
        }
    }
}

这儿有相当多的工作要做,所以让咱们把它分化一下:

  1. 你界说了 Amplifier 类。这也是一个根类,就像 Instrument 相同。
  2. 存储特点 _volume 被标记为 private,因而它只能在 Amplifier 类中被拜访,并被躲藏起来,不被外部用户发现。称号最初的下划线强调了它是一个私有的完成细节。再说一次,这只是一个常规。但遵从常规是好的。:]
  3. 存储特点 isOn 能够被外部用户读取,但不能被写入。这是用 private(set) 来完成的
  4. plugIn()unplug() 影响 isOn 的状况。
  5. 名为 volume计算特点(computed property)包装了私有存储特点 _volume
  6. 假如没有电源刺进,getter 办法会将音量降为0。
  7. setter 办法里边,音量将总是被约束在0到10之间的某个值。不能把放大器设置为 11。

拜访操控关键字 private 关于躲藏杂乱性和维护你的类不受无效修正的影响十分有用。它的花名是 “维护不变量”。不变量指的是一个操作应该一直保存的真理。

组合

现在你有了一个方便的放大器组件,是时分在电吉他中运用它了。将 ElectricGuitar 类的完成增加到 Playground 的末尾,紧接着 Amplifier 类的声明:

// 1
class ElectricGuitar: Guitar {
    // 2
    let amplifier: Amplifier
    // 3
    init(brand: String, stringGauge: String = "light", amplifier: Amplifier) {
        self.amplifier = amplifier
        super.init(brand: brand, stringGauge: stringGauge)
    }
    // 4
    override func tune() -> String {
        amplifier.plugIn()
        amplifier.volume = 5
        return "Tune (brand) electric with E A D G B E"
    }
    // 5
    override func play(_ music: Music) -> String {
        let preparedNotes = super.play(music)
        return "Play solo (preparedNotes) at volume (amplifier.volume)."
    }
}

一步一步来吧:

  1. ElectricGuitar 是一个详细类型,它派生自笼统的、中间层基类 Guitar
  2. 一个电吉他包括一个放大器。这是一种 has-a 的联系,而不是像承继那样的 is-a 联系。
  3. 一个自界说的初始化器,初始化一切存储的特点,然后调用超类。
  4. 一个合理的 tune() 办法。
  5. 一个合理的 play() 办法。

以类似的办法,将 BassGuitar 类声明增加到 Playground 的底部,紧接着 ElectricGuitar 类的完成:

class BassGuitar: Guitar {
    let amplifier: Amplifier
    init(brand: String, stringGauge: String = "heavy", amplifier: Amplifier) {
        self.amplifier = amplifier
        super.init(brand: brand, stringGauge: stringGauge)
    }
    override func tune() -> String {
        amplifier.plugIn()
        return "Tune (brand) electric with E A D G"
    }
    override func play(_ music: Music) -> String {
        let preparedNotes = super.play(music)
        return "Play bass line (preparedNotes) at volume (amplifier.volume)."
    }
}

这就创造了一个也利用了一个(有一个)放大器的低声吉他。阶级遏止的举动。是时分进行另一次应战了!

应战:

你或许听说过,类是遵从引证语义的。这意味着持有一个类实例的变量实践上持有该实例的引证。假如你有两个具有相同引证的变量,改变一个变量的数据将改变另一个变量的数据,这实践上是同一件事。经过实例化一个放大器并在 Gibson 电吉他和 Fender 低声吉他之间同享它,来展示引证语义的效果。

let amplifier = Amplifier()
let electricGuitar = ElectricGuitar(brand: "Gibson", stringGauge: "medium", amplifier: amplifier)
electricGuitar.tune()
let bassGuitar = BassGuitar(brand: "Fender", stringGauge: "heavy", amplifier: amplifier)
bassGuitar.tune()
bassGuitar.amplifier.volume
electricGuitar.amplifier.volume
bassGuitar.amplifier.unplug()
bassGuitar.amplifier.volume
electricGuitar.amplifier.volume
bassGuitar.amplifier.plugIn()
bassGuitar.amplifier.volume
electricGuitar.amplifier.volume

多态

面向目标编程的一大优势是能够经过相同的接口运用不同的目标,而每个目标都有自己独特的行为办法。这便是多态性,意味着 “多种形式”。在 Playground 的结尾增加 Band 类的完成:

class Band {
    let instruments: [Instruments] // 声明为 Instruments 乐器基类,实践存储详细子类
    init(instruments: [Instruments]) {
        self.instruments = instruments
    }
    func perform(_ music: Music) {
        for instrument in instruments {
            instrument.perform(music)
        }
    }
}

Band 类有一个乐器数组的存储特点,你能够在初始化器中设置。乐队在舞台上进行现场扮演,经过 for in 循环遍历乐器数组,为数组中的每件乐器调用 perform(_:) 办法。

现在,持续准备你的第一次摇滚音乐会。在 Playground 的底部,紧接着 Band 类的完成,增加以下代码块:

let instruments = [piano, acousticGuitar, electricGuitar, bassGuitar]
let band = Band(instruments: instruments)
band.perform(music)

你首要从你从前创立的 Instrument 类实例中界说一个 instruments 数组。然后你声明 band 目标,并用乐队初始化器配置其 instruments 特点。终究你运用 band 实例的 perform(_:) 办法使乐队进行现场音乐扮演(打印调音和演奏的结果)。

注意,虽然 instruments 数组的类型是 [Instrument],但每个乐器都根据其**类的类型(class type)**进行相应的扮演。这便是多态性在实践中的效果:你现在能够在现场表演中像个专家相同扮演了! :]

注意:假如你想了解更多关于类的信息,请检查咱们的 Swift 枚举、结构和类的教程。

拜访操控

你现已看到了 private 的效果,它是一种躲藏杂乱性和维护你的类不被无意中进入无效状况(即破坏不变性)的办法。Swift 更进一步,供给了四个等级的拜访操控,包括:

  • **private:**仅在类的内部可见。
  • **fileprivate:**可从同一文件中的任何当地看到。
  • **internal:**在同一模块或应用程序中的任何当地都能够看到。
  • **public:**在模块外的任何当地都能够看到。

还有其他与拜访操控有关的关键字:

  • **open:**可在模块外任何当地运用。不只能够在模块外的任何当地运用,并且还能够从外部进行子类化或重写。
  • **final:**不能被重写或子类化。

假如你没有指定一个类、特点或办法的拜访操控权限,它默认为 internal。因为你通常只要一个单一的模块,这让你在开端时疏忽了拜访操控的问题。只要当你的应用程序变得更大、更杂乱,你需求考虑躲藏一些杂乱性时,你才真实需求开端忧虑这个问题。

制作一个结构

假设你想制作你自己的音乐和乐器结构。你能够经过向你的 Playground 的编译源增加界说来模仿这一点。首要,从 Playground 中删去 MusicInstrument 的界说。这将导致许多过错,你现在要修正这些过错。

经过进入 View/Navigators/Show Project Navigator,确保项目导航器在 Xcode 中是可见的。然后右击 Sources 文件夹,从菜单中挑选 New File。重命名文件 MusicKit.swift 并删去里边的一切内容。将其内容替换为:

// 1
final public class Music {
    // 2
    public let notes: [String]
    public init(notes: [String]) {
        self.notes = notes
    }
    public func prepared() -> String {
        return notes.joined(separator: " ")
    }
}
// 3
open class Instrument {
  public let brand: String
  public init(brand: String) {
    self.brand = brand
  }
  // 4
  open func tune() -> String {
    fatalError("Implement this method for (brand)")
  }
  open func play(_ music: Music) -> String {
    return music.prepared()
  }
  // 5
  final public func perform(_ music: Music) {
    print(tune())
    print(play(music))
  }
}

保存文件并切换回你的 Playground 的主页。这将持续像以前相同工作。这儿有一些关于你在这儿所做的工作的阐明。

  1. final public 意味着它将被一切外部人员看到,但你不能对它进行子类化。
  2. 假如你想从外部看到它,每个存储特点、初始化器、办法都必须被标记为 public
  3. Instrument 类被标记为 public,因为答应子类化。
  4. 办法也能够被标记为 open,以答应重写。
  5. 办法能够被标记为 final,所以没有人能够掩盖它们。这或许是一个有用的确保。

何去何从?

你能够下载本教程的终究 Playground,其中包括了本教程的示例代码。

你能够在咱们的《swift Apprentice》一书中阅览更多关于面向目标编程的内容,或者经过咱们的《设计形式教程》一书来进一步应战自己。

我希望你喜爱这个教程,假如你有任何问题或意见,请参加下面的论坛评论!

附录

MusicKit.swift

// 音乐
final public class Music {
  public let notes: [String]
  public init(notes: [String]) {
    self.notes = notes
  }
  public func prepared() -> String {
    return notes.joined(separator: " ")
  }
}
// 乐器基类
open class Instrument {
  public let brand: String
  public init(brand: String) {
    self.brand = brand
  }
  // Concrete classes must override this method.
  open func tune() -> String {
    fatalError("Implement this method for (brand)")
  }
  // Derived classes must call the superclass
  // their own specialization.
  open func play(_ music: Music) -> String {
    return music.prepared()
  }
  final public func perform(_ music: Music) {
    print(tune())
    print(play(music))
  }
}

Instruments

// 钢琴
class Piano: Instrument {
  let hasPedals: Bool
  static let whiteKeys = 52
  static let blackKeys = 36
  init(brand: String, hasPedals: Bool = false) {
    self.hasPedals = hasPedals
    super.init(brand: brand)
  }
  override func tune() -> String {
    return "Piano standard tuning for (brand)."
  }
  override func play(_ music: Music) -> String {
    return play(music, usingPedals: hasPedals)
  }
  func play(_ music: Music, usingPedals: Bool) -> String {
    let preparedNotes = super.play(music)
    if hasPedals && usingPedals {
      return "Play piano notes (preparedNotes) with pedals."
    }
    else {
      return "Play piano notes (preparedNotes) without pedals."
    }
  }
}
let piano = Piano(brand: "Yamaha", hasPedals: true)
piano.tune()
let music = Music(notes: ["C", "G", "F"])
piano.play(music, usingPedals: false)
piano.play(music)
Piano.whiteKeys
Piano.blackKeys
// 吉他
class Guitar: Instrument {
  let stringGauge: String
  init(brand: String, stringGauge: String = "medium") {
    self.stringGauge = stringGauge
    super.init(brand: brand)
  }
}
// 原声吉他
class AcousticGuitar: Guitar {
  static let numberOfStrings = 6
  static let fretCount = 20
  override func tune() -> String {
    return "Tune (brand) acoustic with E A D G B E"
  }
  override func play(_ music: Music) -> String {
    let preparedNotes = super.play(music)
    return "Play folk tune on frets (preparedNotes)."
  }
}
let acousticGuitar = AcousticGuitar(brand: "Roland", stringGauge: "light")
acousticGuitar.tune()
acousticGuitar.play(music)
// 放大器
class Amplifier {
  private var _volume: Int
  private(set) var isOn: Bool
  init() {
    isOn = false
    _volume = 0
  }
  func plugIn() {
    isOn = true
  }
  func unplug() {
    isOn = false
  }
  var volume: Int {
    get {
      return isOn ? _volume : 0
    }
    set {
      _volume = min(max(newValue, 0), 10)
    }
  }
}
// 电子吉他
class ElectricGuitar: Guitar {
  let amplifier: Amplifier
  static let numberOfStrings = 6
  static let fretCount = 24
  init(brand: String, stringGauge: String = "light", amplifier: Amplifier) {
    self.amplifier = amplifier
    super.init(brand: brand, stringGauge: stringGauge)
  }
  override func tune() -> String {
    amplifier.plugIn()
    amplifier.volume = 5
    return "Tune (brand) electric with E A D G B E"
  }
  override func play(_ music: Music) -> String {
    let preparedNotes = super.play(music)
    return "Play solo (preparedNotes) at volume (amplifier.volume)."
  }
}
// 贝斯吉他
class BassGuitar: Guitar {
  let amplifier: Amplifier
  static let numberOfStrings = 4
  static let fretCount = 24
  init(brand: String, stringGauge: String = "heavy", amplifier: Amplifier) {
    self.amplifier = amplifier
    super.init(brand: brand, stringGauge: stringGauge)
  }
  override func tune() -> String {
    amplifier.plugIn()
    return "Tune (brand) bass with E A D G"
  }
  override func play(_ music: Music) -> String {
    let preparedNotes = super.play(music)
    return "Play bass line (preparedNotes) at volume (amplifier.volume)."
  }
}
// !!!: 类遵从引证语义
let amplifier = Amplifier()
let electricGuitar = ElectricGuitar(brand: "Gibson", stringGauge: "medium", amplifier: amplifier)
electricGuitar.tune()
let bassGuitar = BassGuitar(brand: "Fender", stringGauge: "heavy", amplifier: amplifier)
bassGuitar.tune()
// Notice that because of class reference semantics, the amplifier is a shared
// resource between these two guitars.
bassGuitar.amplifier.volume
electricGuitar.amplifier.volume
bassGuitar.amplifier.unplug()
bassGuitar.amplifier.volume
electricGuitar.amplifier.volume
bassGuitar.amplifier.plugIn()
bassGuitar.amplifier.volume
electricGuitar.amplifier.volume
// 乐队
final class Band {
  let instruments: [Instrument]
  init(instruments: [Instrument]) {
    self.instruments = instruments
  }
  func perform(_ music: Music) {
    for instrument in instruments {
      instrument.perform(music)
    }
  }
}
// 多态类型
let instruments = [piano, acousticGuitar, electricGuitar, bassGuitar]
let band = Band(instruments: instruments)
band.perform(music)