自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Swift 中的抽象類型和方法

移動開發(fā) iOS
抽象類型與普通類型的區(qū)別在于,它們永遠(yuǎn)不會被當(dāng)作原樣使用(事實上,一些編程語言甚至阻止抽象類型被直接實例化),因為它們的唯一目的是作為一組相關(guān)類型的共同父類。

在面向?qū)ο蟮木幊讨?,抽象類型提供了一個基礎(chǔ)實現(xiàn),其他類型可以從中繼承,以獲得某種共享的、共同的功能。抽象類型與普通類型的區(qū)別在于,它們永遠(yuǎn)不會被當(dāng)作原樣使用(事實上,一些編程語言甚至阻止抽象類型被直接實例化),因為它們的唯一目的是作為一組相關(guān)類型的共同父類。

例如,假設(shè)我們想統(tǒng)一我們通過網(wǎng)絡(luò)加載某些類型的模型的方式,通過提供一個共享的API,我們將能夠用來分離關(guān)注點,使依賴注入[1]和模擬[2]變得容易,并在我們的項目中保持方法名稱的一致性。

一個基于抽象類型的方法是使用一個基類,它將作為我們所有模型加載類型的共享、統(tǒng)一的接口。因為我們不希望這個類被直接使用,所以我們要讓它在基類的實現(xiàn)被錯誤調(diào)用時觸發(fā)一個fatalError:

class Loadable<Model> {
func load(from url: URL) async throws -> Model {
fatalError("load(from:) has not been implemented")
}
}

然后,每個Loadable子類將重載上述load方法,以提供其加載功,如下所示:

如果上述模式看起來很熟悉,那可能是因為它本質(zhì)上與我們在Swift 中通常使用的協(xié)議[3]的多態(tài)性完全相同。也就是說,當(dāng)我們想定義一個接口,一個契約,多個類型可以通過不同的實現(xiàn)來遵守。

class UserLoader: Loadable<User> {
override func load(from url: URL) async throws -> User {
...
}
}

不過,協(xié)議確實比抽象類有一個顯著的優(yōu)勢,因為編譯器將強(qiáng)制它們的所有需求都得到正確實現(xiàn)——這意味著我們不再需要依賴運行時錯誤(例如 fatalError)來防止不當(dāng)使用,因為我們無法實例化協(xié)議。

因此,如果我們采用面向協(xié)議的方案,而不是使用抽象基類,那么我們之前的 Loadable 和 UserLoader 類型可能看起來像這樣:

protocol Loadable {
associatedtype Model
func load(from url: URL) async throws -> Model
}
class UserLoader: Loadable {
func load(from url: URL) async throws -> User {
...
}
}

請注意我們現(xiàn)在是如何使用一個相關(guān)的類型來使每個Loadable實現(xiàn)決定它想要加載的確切Model的——這給了我們一個在完全類型安全和巨大靈活性之間的很好的綜合。

所以,一般來說,協(xié)議肯定是在Swift中聲明抽象類型的首選方式,但這并不意味著它們是完美的。事實上,我們基于協(xié)議的Loadable實現(xiàn)目前有兩個主要缺點:

  • 首先,由于我們不得不為我們的協(xié)議添加一個相關(guān)的類型,以保持我們的設(shè)計是泛型的和類型安全的,這意味著Loadable不能再被直接引用了。
  • 其次,由于協(xié)議不能包含任何形式的存儲。如果我們想添加任何存儲屬性,讓所有的Loadable實現(xiàn)都能使用,我們就必須在每一個具體的實現(xiàn)中重新聲明這些屬性。

這個屬性存儲方面確實是我們以前基于抽象類設(shè)計的一個巨大優(yōu)勢。因此,如果我們將Loadable還原成一個類,那么我們就能夠?qū)⑽覀兊淖宇愃枰乃袑ο笾苯哟鎯υ谖覀兊幕愔小辉傩枰诙喾N類型中重復(fù)聲明這些屬性:

class Loadable<Model> {
let networking: Networking
let cache: Cache<URL, Model>
init(networking: Networking, cache: Cache<URL, Model>) {
self.networking = networking
self.cache = cache
}
func load(from url: URL) async throws -> Model {
fatalError("load(from:) has not been implemented")
}
}
class UserLoader: Loadable<User> {
override func load(from url: URL) async throws -> User {
if let cachedUser = cache.value(forKey: url) {
return cachedUser
}
let data = try await networking.data(from: url)
...
}
}

所以,我們在這里處理的基本上是一個典型的權(quán)衡方案,兩種方法(抽象類與協(xié)議)都給我們帶來了不同的優(yōu)點和缺點。但是,如果我們能把這兩種方法結(jié)合起來,得到兩個方案的優(yōu)點,會怎么樣呢?

如果我們仔細(xì)想想,基于抽象類的方法唯一真正的問題是,我們必須在每個子類需要實現(xiàn)的方法中加入fatalError,那么如果我們只為這個特定的方法使用一個協(xié)議呢?那么我們?nèi)匀豢梢栽诨愔斜A粑覀兊膎etworking 和cache 屬性——像這樣:

protocol LoadableProtocol {
associatedtype Model
func load(from url: URL) async throws -> Model
}
class LoadableBase<Model> {
let networking: Networking
let cache: Cache<URL, Model>
init(networking: Networking, cache: Cache<URL, Model>) {
self.networking = networking
self.cache = cache
}
}

但這種方法的主要缺點是,所有的具體實現(xiàn)現(xiàn)在都必須對LoadableBase進(jìn)行子類化,并聲明它們符合我們新的LoadableProtocol協(xié)議:

class UserLoader: LoadableBase<User>, LoadableProtocol {
...
}

這可能不是一個巨大的問題,但可以說它確實使我們的代碼不那么優(yōu)雅。不過,好消息是,我們實際上可以通過使用通用類型別名來解決這個問題。由于Swift的組合運算符&支持將一個類和一個協(xié)議結(jié)合起來,我們可以將我們的Loadable類型作為LoadableBase和LoadableProtocol之間的組合重新引入:

typealias Loadable<Model> = LoadableBase<Model> & LoadableProtocol

這樣,具體的類型(如UserLoader)可以簡單地聲明它們是基于Loadable的,而編譯器將確保所有這些類型實現(xiàn)我們協(xié)議的load方法——同時仍然使這些類型能夠使用我們基類中聲明的屬性:

class UserLoader: Loadable<User> {
func load(from url: URL) async throws -> User {
if let cachedUser = cache.value(forKey: url) {
return cachedUser
}
let data = try await networking.data(from: url)
...
}
}

很好! 上述方法的唯一真正的缺點是,Loadable仍然不能被直接引用,因為它仍然是部分的泛型協(xié)議。但這實際上可能不是一個問題——如果這成為一種情況,那么我們總是可以使用諸如類型擦除的技術(shù)來解決這些問題。

對于我們新的基于類型別名的Loadable設(shè)計方案,另一個輕微的警告是這種組合類型別名不能被擴(kuò)展,如果我們想提供一些我們不想(或不能)在LoadableBase類中直接實現(xiàn)的便利API,這可能會成為一個問題。

不過,解決這個問題的一個方法是,在我們的協(xié)議中聲明實現(xiàn)這些便利API所需要的一切,這將使我們能夠自行擴(kuò)展該協(xié)議:

protocol LoadableProtocol {
associatedtype Model
var networking: Networking { get }
var cache: Cache<URL, Model> { get }
func load(from url: URL) async throws -> Model
}
extension LoadableProtocol {
func loadWithCaching(from url: URL) async throws -> Model {
if let cachedModel = cache.value(forKey: url) {
return cachedModel
}
let model = try await load(from: url)
cache.insert(model, forKey: url)
return model
}
}
}

這就是在Swift中使用抽象類型和方法的幾種不同方式。子類化目前可能不像以前那樣流行(在其他編程語言中也是如此),但我仍然認(rèn)為這些技術(shù)在我們整個Swift開發(fā)工具箱中是非常好的。

參考資料

責(zé)任編輯:姜華 來源: Swift社區(qū)
相關(guān)推薦

2011-06-28 10:55:20

C#接口抽象類

2010-03-04 09:40:04

Java接口抽象類

2011-07-10 14:07:59

JAVA

2009-06-16 11:30:00

Java抽象類Java接口

2009-06-14 21:31:29

Java抽象類Java接口

2011-12-22 10:48:21

Java

2011-05-19 18:01:56

JAVA

2010-07-06 08:58:52

UML圖表達(dá)C++

2012-02-29 09:32:01

Java

2011-07-12 15:58:48

java抽象類接口

2009-04-30 15:15:01

Java抽象類接口

2020-10-19 13:03:16

Java 8接口抽象類

2011-07-06 10:33:31

C#

2015-03-23 09:33:43

Java抽象類Java接口Java

2022-05-11 09:01:54

Swift類型系統(tǒng)幻象類型

2009-08-10 10:04:25

C#抽象類C#接口

2009-07-30 18:36:00

C#接口C#抽象類

2009-08-03 18:12:31

C#抽象類

2009-07-22 07:50:00

Scala二維布局庫抽象類

2009-08-14 15:54:17

C#接口和抽象類
點贊
收藏

51CTO技術(shù)棧公眾號