Swift Collection协议默认实现优化:隐藏内部关联集合变量的方法
解决Collection实现复用与私有存储的问题
你的思路非常棒——通过协议扩展来复用Collection的默认实现确实是Swift中代码复用的绝佳方式。针对你提到的_items无法真正私有隐藏的问题,我们可以通过调整协议的访问控制来解决,同时还有其他更优的无继承复用方案,下面详细说明:
方案一:私有协议+隐式遵守,彻底隐藏存储属性
核心思路是把自定义的CollectionTrait协议设置为文件私有(fileprivate)或私有(private),这样外部代码完全看不到这个协议;同时让你的类内部实现私有存储属性,再通过同文件的extension让类遵守这个私有协议,从而自动获得Collection的默认实现。
代码示例:
// 1. 将协议设为fileprivate,仅当前文件可见 fileprivate protocol CollectionTrait: Collection { associatedtype CollectionType: Collection var _items: CollectionType { get } } // 2. 协议扩展实现所有Collection必需方法,转发到_items extension CollectionTrait { var startIndex: CollectionType.Index { _items.startIndex } var endIndex: CollectionType.Index { _items.endIndex } func index(after i: CollectionType.Index) -> CollectionType.Index { _items.index(after: i) } subscript(index: CollectionType.Index) -> CollectionType.Element { _items[index] } } // 3. 你的业务类只需公开遵守Collection,存储属性设为private class Words: Collection { // 真正私有,外部完全无法访问 private let _items = ["foo", "bar", "baz"] } // 4. 在同文件中添加extension,让类遵守私有协议(外部看不到这一步) extension Words: CollectionTrait {} // 测试:外部可以正常使用Collection的所有功能,看不到_items let words = Words() for word in words { print(word) } print(words.count) // 自动继承Collection的count方法
为什么这个方案有效?因为私有协议的要求仅在当前文件内可见,类的_items设为private后,只有同文件的协议扩展能访问到它,外部代码既看不到CollectionTrait,也看不到_items,完美实现了封装。
方案二:泛型组合封装(适用于固定存储类型场景)
如果你的大多数类都是用数组作为底层存储,还可以创建一个泛型结构体来封装Collection的实现,然后让业务类持有这个结构体的私有实例,再通过extension自动转发Collection的方法:
// 私有泛型结构体,封装数组的Collection实现 private struct ArrayCollection<Element>: Collection { private let items: [Element] init(_ items: [Element]) { self.items = items } var startIndex: Int { items.startIndex } var endIndex: Int { items.endIndex } func index(after i: Int) -> Int { items.index(after: i) } subscript(index: Int) -> Element { items[index] } } // 业务类持有私有ArrayCollection实例 class Words: Collection { private let collection = ArrayCollection(["foo", "bar", "baz"]) // 转发Collection的所有必需属性/方法到私有实例 var startIndex: Int { collection.startIndex } var endIndex: Int { collection.endIndex } func index(after i: Int) -> Int { collection.index(after: i) } subscript(index: Int) -> String { collection[index] } }
这个方案的好处是不需要自定义协议,但缺点是需要手动转发Collection的方法(不过如果用方案一的私有协议,就可以省去转发代码)。
总结对比
- 方案一:最灵活,支持任意Collection类型的底层存储(数组、Set甚至自定义Collection),代码复用率最高,完全隐藏实现细节,推荐优先使用。
- 方案二:适合固定用数组作为存储的场景,代码更直观,但需要手动转发方法,复用性稍弱。
两种方案都不需要继承,完美适配你的场景。
内容的提问来源于stack exchange,提问作者Haagenti
相关产品推荐
相关产品推荐

