首页 » iOS编程基础:Swift、Xcode和Cocoa入门指南 » iOS编程基础:Swift、Xcode和Cocoa入门指南全文在线阅读

《iOS编程基础:Swift、Xcode和Cocoa入门指南》4.5 多态

关灯直达底部

如果某个计算机语言有类型与子类型层次,那么它必须要解决这样一个问题:对于对象类型与声明的指向该对象的引用类型之间的关系,这种层次体系意味着什么。Swift遵循着多态的原则。我认为,正是多态的作用才使得基于对象的语言彻底演变为完善的面向对象语言。Swift的多态原则如下所示:

替代

在需要某个类型时,我们可以使用该类型的子类型。

内在一致性

对象类型的关键在于内部特性,而与对象是如何被引用的毫无关系。

下面来看看这些原则的含义。假设有一个Dog类,它有一个子类NoisyDog:


class Dog {}class NoisyDog : Dog {}let d : Dog = NoisyDog  

替代法则表示上述代码最后一行是合法的:我们可以将NoisyDog实例赋给类型为Dog的引用d。内在一致性法则表示,在底层d现在就是个NoisyDog。

你可能会问:如何证明内在一致性规则?如果对NoisyDog的引用类型是Dog,那么它怎么就是NoisyDog呢?为了说明这一问题,我们来看看当子类重写了继承下来的方法时会发生什么。下面重新定义Dog与NoisyDog进行说明:


class Dog {    func bark {        print(/"woof/")    }}class NoisyDog : Dog {    override func bark {        super.bark; super.bark    }}  

看看下面的代码,想想能否编译通过,如果能,那么结果是什么:


func tellToBark(d:Dog) {    d.bark}var d = NoisyDogtellToBark(d)  

上述代码可以编译通过。我们创建了一个NoisyDog实例并将其传给了需要Dog参数的函数。这么做是可以的,因为NoisyDog是Dog的子类(替代)。NoisyDog可以用在需要Dog的地方。从类型上来看,NoisyDog就是一种Dog。

不过当代码实际运行并调用tellToBark函数时,它里面的局部变量d所引用的对象的bark会做什么呢?一方面,d的类型是Dog,Dog的bark函数会打印出/"woof/"一次;另一方面,当调用tellToBark时,实际传递的是NoisyDog实例,而NoisyDog的bark函数会打印出/"woof/"两次,那结果会是什么呢?下面就来看一下:


func tellToBark(d:Dog) {    d.bark}var d = NoisyDogtellToBark(d) // woof woof  

结果是/"woof woof/"。内在一致性法则表明在发送消息时,重要的事情并不是如何通过引用来判断消息接收者的类型,而是接收者的实际类型到底是什么。无论持有的变量类型是什么,传递给tellToBark的是NoisyDog;因此,bark消息会使得该对象打印出两次/"woof/"。它是个NoisyDog!

下面是多态的另一个重要影响:关键字self的含义。它指的是实际实例,其含义取决于实际实例的类型,即便单词self出现在父类代码中亦如此。比如:


class Dog {    func bark {        print(/"woof/")    }    func speak {        self.bark    }}class NoisyDog : Dog {    override func bark {        super.bark; super.bark    }}  

调用NoisyDog的speak方法时会打印什么呢?下面来试一下:


let d = NoisyDogd.speak // woof woof  

speak方法声明在Dog而非NoisyDog中,即声明在父类中。speak方法会调用bark方法,这是通过关键字self实现的(这里其实可以省略对self的显式引用,不过即便如此,self还是会隐式使用,因此我还是显式使用了self)。Dog中有个bark方法,NoisyDog中有个重写的bark方法。那么到底会调用哪个bark方法呢?

关键字self位于Dog类的speak方法实现中。不过,重要的事情并不是单词self在哪里,而是它表示什么含义。它表示当前实例。内在一致性法则告诉我们,当前实例是个NoisyDog!因此,调用的是NoisyDog重写的bark。

归功于多态,你可以充分利用子类为已有的类增加功能并做更多的定制。这在iOS编程世界中尤为重要,其中大多数类都是由Cocoa定义的,并不属于你。比如,UIViewController是由Cocoa定义的;它有大量Cocoa会调用的内建方法,这些方法会执行各种重要的任务,而且是以一种通用的方式执行的。在实际情况中,你会声明UIViewController的子类并重写这些方法来完成适合于特定应用的任务。这对Cocoa不会造成任何影响,因为替代法则在发挥着作用,在Cocoa期望接收或要调用UIViewController时,它可以接收你自己定义的UIViewController子类,这么做不会产生任何问题。而且,这种替代行为与你的期望是一致的,因为(内在一致性法则)当Cocoa调用子类中的UIViewController方法时,真正调用的实际上是子类重写的版本。

多态很酷,不过其速度会慢一些。它需要动态分发,这意味着运行时要思考向类实例发送的消息到底表示什么。这也是在可能的情况下优先使用结构体而非类的另一个原因:结构体无须动态分发。此外,可以通过将类或类成员声明为final或private,以及打开全模块优化(参见第6章)来减少动态分发的使用。