Swift中的抽象类型和方法

Swift中的抽象类型和方法

在面向对象编程中,抽象类型提供了一个基本实现,其他类型可以从中继承,以便访问某种共享的通用功能。抽象类型与常规类型的区别在于,它们从未打算按原样使用(事实上,一些编程语言甚至阻止抽象类型直接实例化),因为它们的唯一目的是作为一组相关类型的共同父亲。

例如,假设想统一在网络上加载某些类型模型的方式,通过提供一个共享的API,能够用它来分离关注点,实施依赖注入和模拟测试,并在整个项目中保持一致方法名称。

一种基于抽象类型的方法是使用一个基类,该基类将作为所有模型加载类型的共享、统一的接口。由于不希望该类被直接使用,如果错误地调用其基本实现,我们将使其触发fatalError

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

然后,每个Loadable子类将覆盖上述load方法,以提供其加载功能——如下:

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

如果上述模式看起来很熟悉,那可能是因为它本质上与Swift中协议多态性完全相同。也就是说,当定义一个接口,一个合约时,多个类型可以通过不同的实现来符合。

然而,协议确实比抽象类具有显著优势,因为编译器将强制其所有要求都得到恰当实现——这意味着我们不再需要依赖运行时错误(如 fatalError)来防止不当使用,因为没有办法单独实例化协议。

因此,如果采用面向协议的路线,而不是使用抽象基类,之前的LoadableUserLoader类型可能看起来是这样的样:

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

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

请注意,通过使用协议关联类型,每个Loadable的实现能够决定它想要加载的确切模型——这是一个完整类型安全和巨大灵活性之间的良好组合。

因此,一般来说,协议绝对是在Swift中声明抽象类型的首选方式,但这并不意味着它们是完美的。事实上,基于协议的Loadable实现目前有两个主要缺点:

  • 首先,由于必须在协议中添加一个关联类型,以保持设置通用和类型安全,这意味着Loadable不能再直接引用。

  • 其次,由于协议不能包含任何形式的存储,如果想添加所有Loadable实现都可以使用的任何存储属性,必须在每个具体实现中重新声明这些属性。

该属性存储方面确实是基于抽象类方案的巨大优势。因此,如果要将可Loadabel恢复到一个类,那么就能够将子类所需的所有对象存储在我们的基类本身中——无需跨多个类型复制这些属性:

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)
        ...
    }
}

因此,在这里处理的本质上是一个经典的权衡方案,其中两种方法(抽象类与协议)都给了我们一套不同的利弊。但是,如果我们能将两者结合起来,获得两全其美呢?

如果我们想一想,基于抽象类的方法的唯一真正问题是,必须在每个子类需要实现的方法中添加fatalError,那么如果只为该特定方法使用协议呢?然后,仍然可以将网络和缓存属性保留在基类中——像这样:

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
    }
}

然而,这种方法的主要缺点是,所有具体实现现在都必须是LoadableBase的子类,并声明它们符合新的LoadableProtocol

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

这可能不是一个大问题,但它确实让代码变得不那么优雅。不过,好消息是,实际上可以通过范型类型别名来解决这个问题。由于Swift的合成运算符&支持将类与协议结合,可以重新引入Loadable类型作为LoadableBaseLoadableProtocol的组合:

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

这样,具体类型(如UserLoader)可以简单地声明它们是基于Loadable的,编译器将确保所有此类类型实现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仍然不能直接引用,因为它幕后仍然是部分范型协议。这实际上可能不是一个问题——如果情况变得如此,总是可以使用类型删除等技术来解决这些问题。

另一个小警告是, 基于新类型别名的Loadable是一种组合类型别名,无法扩展,如果想提供一些便利API, 同时又不想(或不能)直接在LoadableBase类中实现 ,这可能会成为一个问题。

然而,解决这个问题的一种方法是在协议中声明实现这些便利API所需的一切,然后自行扩展该协议:

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中使用抽象类型和方法的几种不同方法。子类化目前可能不像以前那么受欢迎(并且仍然在其他编程语言中),但我仍然认为这些技术在整体Swift开发工具箱中很棒。

如果您有任何问题、评论或反馈,请随时通过推特电子邮件告诉我。

感谢您的阅读!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值