Swift入门:多态性与类型转换

2020-03-19 20:46:44 浏览数 (1)

多态性

因为类可以相互继承(例如CountrySinger可以从Singer继承),这意味着一个类实际上是另一个类的超集:B类拥有A类所拥有的所有东西,还有一些额外的东西。这反过来意味着你可以把B当作B型或者A型,这取决于你的需要。

感到困惑?让我们尝试一些代码:

代码语言:javascript复制
class Album {
    var name: String

    init(name: String) {
        self.name = name
    }
}

class StudioAlbum: Album {
    var studio: String

    init(name: String, studio: String) {
        self.studio = studio
        super.init(name: name)
    }
}

class LiveAlbum: Album {
    var location: String

    init(name: String, location: String) {
        self.location = location
        super.init(name: name)
    }
}

它定义了三个类:AlbumStudioAlbumLiveAlbum,后两个都继承自Album。因为LiveAlbum的实例是从Album继承来的,所以可以将其视为AlbumLiveAlbum,两者同时存在。这被称为“多态性”,它意味着您可以编写如下代码:

代码语言:javascript复制
var taylorSwift = StudioAlbum(name: "Taylor Swift", studio: "The Castles Studios")
var fearless = StudioAlbum(name: "Speak Now", studio: "Aimeeland Studio")
var iTunesLive = LiveAlbum(name: "iTunes Live from SoHo", location: "New York")

var allAlbums: [Album] = [taylorSwift, fearless, iTunesLive]

在这里,我们创建了一个数组,它只保存Album,但里面放了两个StudioAlbum和一个LiveAlbum。因为它们都是Album类的子类,所以它们共享相同的基本行为。

我们可以进一步说明多态性是如何工作的。让我们将getPerformance()方法添加到所有三个类中:

代码语言:javascript复制
class Album {
    var name: String

    init(name: String) {
        self.name = name
    }

    func getPerformance() -> String {
        return "The album (name) sold lots"
    }
}

class StudioAlbum: Album {
    var studio: String

    init(name: String, studio: String) {
        self.studio = studio
        super.init(name: name)
    }

    override func getPerformance() -> String {
        return "The studio album (name) sold lots"
    }
}

class LiveAlbum: Album {
    var location: String

    init(name: String, location: String) {
        self.location = location
        super.init(name: name)
    }

    override func getPerformance() -> String {
        return "The live album (name) sold lots"
    }
}

getPerformance()方法存在于Album类中,但两个子类都覆盖它。当我们创建一个保存Album的数组时,实际上是用Album的子类来填充它:LiveAlbumStudioAlbum。他们进入数组很好,因为他们继承了Album类,但他们从来没有失去原来的类。所以,我们可以这样编写代码:

代码语言:javascript复制
var taylorSwift = StudioAlbum(name: "Taylor Swift", studio: "The Castles Studios")
var fearless = StudioAlbum(name: "Speak Now", studio: "Aimeeland Studio")
var iTunesLive = LiveAlbum(name: "iTunes Live from SoHo", location: "New York")

var allAlbums: [Album] = [taylorSwift, fearless, iTunesLive]

for album in allAlbums {
    print(album.getPerformance())
}

它将根据子类的类型自动使用getPerformance()对应子类中的重写版本。这就是函数调用中的多态性:一个对象可以同时作为本类和父类使用。

用类型转换转换类型

你会经常发现你有一个特定类型的对象,但实际上你知道它是另一种类型。不幸的是,如果Swift不知道你知道什么,它就不会构建你的代码。所以,有一个解决方案,叫做类型转换:将一种类型的对象转换成另一种类型。

很可能你正在努力思考为什么这是必要的,但我可以给你一个非常简单的例子:

代码语言:javascript复制
for album in allAlbums {
    print(album.getPerformance())
}

那是我们几分钟前的循环。allAlbums数组包含类型Album,但我们知道它实际上包含一个子类:StudioAlbumLiveAlbum。Swift 不知道这一点,所以如果你试图写一些像print(album.studio)这样的东西,它会拒绝构建,因为只有StudioAlbum对象才有这个属性。

用Swift打字有三种形式,但大多数时候你只会遇到两种:as? 还有 as!,称为可选转换强制转换

as? : 我认为这个转换可能是真的,但可能会失败; a! : 我知道这个转换是真的,如果我错了,我很高兴因为我的应用程序会崩溃 ???。

注意:当我们说“转换”的时候,我并不是说对象真的被转换了。相反,它只是转换Swift对待对象的方式——你告诉Swift它认为是A型的对象实际上是E型的。

问号和感叹号应该给你一个提示,告诉你发生了什么,因为这和可选区域非常相似。例如,如果您这样写:

代码语言:javascript复制
for album in allAlbums {
    let studioAlbum = album as? StudioAlbum
}

斯威夫特将使studioAlbum转换为数据类型studioAlbum?。也就是说,一个可选的studioAlbum:转换可能已经成功,在这种情况下,你有一个studioAlbum可以让你使用,或者它可能已经失败,在这种情况下,你没有。

这通常与if let一起使用,以自动展开可选结果,如下所示:

代码语言:javascript复制
for album in allAlbums {
    print(album.getPerformance())

    if let studioAlbum = album as? StudioAlbum {
        print(studioAlbum.studio)
    } else if let liveAlbum = album as? LiveAlbum {
        print(liveAlbum.location)
    }
}

这将遍历每个专辑并打印其详细信息,因为这是专辑类及其所有子类的共同点。然后检查它是否可以将唱片集值转换为StudioAlbum,以及是否可以打印出studio名称。对数组中的LiveAlbum也做了同样的事情。

强制转换是指当你确信一种类型的对象可以被当作另一种类型来处理时,但是如果你错了,你的程序就会崩溃。强制转换不需要返回可选值,因为您是说转换肯定会起作用——如果您错了,这意味着您编写的代码是错误的。

为了以比较好的方式演示这一点,让我们去掉LiveAlbum,这样我们就可以在数组中使用StudioAlbum

代码语言:javascript复制
var taylorSwift = StudioAlbum(name: "Taylor Swift", studio: "The Castles Studios")
var fearless = StudioAlbum(name: "Speak Now", studio: "Aimeeland Studio")

var allAlbums: [Album] = [taylorSwift, fearless]

for album in allAlbums {
    let studioAlbum = album as! StudioAlbum
    print(studioAlbum.studio)
}

这显然是一个人为的例子,因为如果那真的是你的代码,你只需改变allAlbums,使其具有数据类型[StudioAlbum]。尽管如此,它还是展示了强制转换的工作原理,并且示例不会崩溃,因为它做出了正确的假设。

Swift允许您将转换作为数组循环的一部分,在本例中,这将更有效。如果您想在数组级别编写强制转换,您可以编写:

代码语言:javascript复制
for album in allAlbums as! [StudioAlbum] {
    print(album.studio)
}

PS: 可以参考Swift编程小技巧中数组内的类型转换,能写出更加Swifty的代码,例如我们使用没有去除LiveAlbumallAlbums:

代码语言:javascript复制
for case let album as StudioAlbum in allAlbums {
  print(album.studio)
}

在for循环内配合case letas关键字,使我们可以排除非StudioAlbum的元素,同时也不用担心程序崩溃的问题,更多方法可查看原文。

本文来自Hacking with Swift 给 swift 初学者的入门课程 Swift for Complete Beginners 的 Polymorphism and typecasting

0 人点赞