Swift - protocol 组合

本文探讨了Swift语言中Any类型的定义方式及其背后的接口组合原理。通过实例展示了如何利用接口组合提高代码的可读性和简洁性,并介绍了在实现多个接口时解决方法冲突的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

众所周知,在Swift中我们可以使用 Any 来表示任意类型。充满好奇心的读者已经发现,Any这个类型的定义十分奇怪,它是一个 protocol<> 的同名类型。

protocol<>这种形式的写法在Swift的日常使用中并不多见,这其实是Swift的接口组合的用法。标准的语法形式是下面这样的:

ProtocolA & ProtocolB & ProtocolC

尖括号内是具体接口的名称,这里表示将名称为ProtocolAProtocolBProtocolC的接口组合在一起的一个新的匿名接口。实现这个匿名接口就意味着要同时实现三个接口所定义的内容。所以说,这里的protocol组合的写法和下面新声明的ProtocolD是相同的:

protocol ProtocolD: ProtocolA, ProtocolB, ProtocolC {

}

那么在Any定义的时候,里面什么都不写的protocol<> 是什么意思呢?从语义上来说,这代表一个“需要实现空接口的接口”,其实就是任意类型的意思了。

除了可以方便的表达空接口这一概念以外,protocol的组合与新创建一个接口相比最大区别就在于其匿名性。有时候我们可以借助这个特性写出清晰代码。因为Swift类型组织比较松散的,你的类型可以由不同的extension 来定义实现不同的接口Swift 也并没有要求它们在同一个文件中。这样,如果一个类型实现了很多接口,在使用这个类型的时候,我们很可能在不查询相关代码的情况下难以知道这个类型所实现的接口。

举个理想化的例子,比如我们有下面的三个接口,分别代表了三种动物叫的方式,而有一种“迷之动物”,同时实现了这三个接口:

protocol KittenLike {
    func meow() -> String
}

protocol DogLike {
    func bark() -> String
}

protocol TigerLike {
    func aou() -> String
}

class MysteryAnimal: KittenLike, DogLike, TigerLike {
    func meow() -> String {
        return "meow"
    }
    
    func bark() -> String {
        return "bark"
    }
    
    func aou() -> String {
        return "aou"
    }
}

现在我们想要检查某种动物作为宠物的时候的叫声的话,我们可能要重新定义一个叫作PetLike 的接口,表明其实现 KittenLike 和 DogLike;如果稍后我们又想检查某种动物作为猫科动物的叫声的话,我们也许又要去定义一个叫作 CatLike 的实现KittenLike和TigerLike的接口,最后我们大概会写出这样的东西:

protocol PetLike: KittenLike, DogLike {
    
}

protocol CatLike: KittenLike, TigerLike {
    
}

struct SoundChecker {
    static func checkPetTalking(pet: PetLike) {
        // ...
    }
    
    static func checkCatTalking(cat: CatLike) {
        // ...
    }
}

虽然没有定义任何新的内容,但是为了实现这个需求,我们还是添加了两个空protocol,这可能会让人困惑,代码的使用者(也包括一段时间后的你自己)可能会去猜测PetLike和CatLike的作用----其实它们除了标注以外并没有其他作用。借助protocol组合的特性,我们可以很好的解决这个问题。protocol组合是可以使用typealias命名的,于是可以将上面的新定义protocol的部分换为:

typealias PetLike = KittenLike & DogLike

typealias CatLike = KittenLike & TigerLike

这样既保持了可读性,也没有多定义不必要的新类型。

另外,如果这两个临时接口我们只用一次的话,只要结合上下文理解起来不会有困难,我们完全可以直接将它们匿名话,变成下面这样:

struct SoundChecker {
    static func checkPetTalking(pet: KittenLike & DogLike) {
        // ...
    }
    
    static func checkCatTalking(cat: KittenLike & TigerLike) {
        // ...
    }
}

这样的好处是定义和使用的地方更加接近,这样在代码复杂的时候,读代码时可以少一些跳转多一些专注。但是因为使用了匿名的接口组合,所以能表达的信息毕竟少一些。如果要实际使用这种方法的话,还是需要多多斟酌

虽然这一节已经够长了,不过我还是想多提一句关于实现多个接口时接口内方法冲突的解决方法。因为在Swift的世界中并没有规定不同接口方法不能重名,所以重名现象是有可能出现的情况。比如有A和B两个接口,定义如下:

protocol A {
    func bar() -> Int
}

protocol B {
    func bar() -> String
}

这两个接口中bar() 只有返回值类型不同。我们如果有一个类型Class同时实现了A 和B,我们要怎么才能避免和解决调用冲突呢?

class Class: A, B {
    func bar() -> Int {
        return 1
    }
    
    func bar() -> String {
        return "Hello"
    }
}

这样一来,对于bar(),只要在调用前进行类型转换就可以了:

let instance = Class()
let num = (instance as A).bar()
let str = (instance as B).bar()
print(num) // 1
print(str) // Hello
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值