Swift面向协议编程简介
Swift面向协议编程简介/** * 谨献给Yoyo * * 原文出处:https://www.toptal.com/swift/introduction-protocol-oriented-programming-swift * @author dogstar.huang <chanzonghuang@gmail.com> 2016-12-06 */ 协议是Swift编程语言中一个非常强大的特性。 协议用于定义“符合某个指定任务或者功能片的方法蓝图,属性,以及其他要求”。 Swift在编译时检查协议一致性问题,使得开发者可以在运行程序前发现代码中的一些致命错误。协议使得开发者可以在Swift编写灵活和可扩展的代码而不用妥协该语言的表现力。 Swfit通过提供一些最常见奇怪问题的解决方案以及许多其他编程语言的接口限制,进一步获得了使用协议的便利性。
在早期的Swfit版本中,只能扩展类、结构以及枚举类型,在很多现代编程语言里也是这样的。然而,自从Swift 2 开始,也能对协议进行扩展了。 此文章会考察在Swfit中协议如何用于编写可重用、可维护的代码,以及通过使用协议扩展如何修改可让单个小模块合并成一个大型面向协议的代码库。 协议什么是协议? 在其最简单的定义里,协议是指描述某些属性和方法的接口。任何符合协议的类型,都应该使用合适的值填充协议中定义的特定属性,并且实现其必要的方法。例如: protocol Queue { var count: Int { get } mutating func push(_ element: Int) mutating func pop() -> Int } 此Queue协议描述了一个队列,它包含了整型的元素。此语法相当明了。 在协议块里面,当描述某个属性时,我们必须指定该属性是只读 如果某个协议要求有一个读写的属性,那就不能用一个存放常量的属性或者一个只读的计算值来填充。 如果协议只要求属性是可读的,那么可以是任意类型的属性,并且该属性也可以是可写的,如果这对于你的代码有用的话。 对于在协议里定义的方法,使用关键词 为了符合协议,类型必须提供全部实例属性以及实现在协议中描述的全部方法。例如下面,是一个符合我们 struct Container: Queue { private var items: [Int] = [] var count: Int { return items.count } mutating func push(_ element: Int) { items.append(element) } mutating func pop() -> Int { return items.removeFirst() } } 然而,我们当前的Queue协议有一堆缺点。 仅有处理 我们可以通过使用“关联类型”特性来去掉这个限制。关联类型的工作方式类似泛型。为了演示,让我们修改Queue协议以采用关联类型: protocol Queue { associatedtype ItemType var count: Int { get } func push(_ element: ItemType) func pop() -> ItemType } 现在此Queue协议允许储存任意类型的元素了。 在这个 class Container<Item>: Queue { private var items: [Item] = [] var count: Int { return items.count } func push(_ element: Item) { items.append(element) } func pop() -> Item { return items.removeFirst() } } 在很多情况下,使用协议都可以简化代码的编写。 例如,任何表示错误的对象都会符合 然后在你的代码里,处理错误的相同逻辑就可以应用到任意这些错误对象上。因此,你不需要使用任何指定的对象来表示错误,用任何符合 你甚至可以扩展String类型,让它符合 extension String: LocalizedError { public var errorDescription: String? { Return NSLocalizedString(self,comment:””) } } throw “Unfortunately something went wrong” func handle(error: Error) { print(error.localizedDescription) } 协议扩展协议扩展基于协议本身的威力。这使得我们可以:
默认的方法实现让我们再来创建一个协议: rotocol ErrorHandler { func handle(error: Error) } 这个协议描述了负责处理应用中出现的错误的对象。例如: struct Handler: ErrorHandler { func handle(error: Error) { print(error.localizedDescription) } } 这里我们只是打印了错误的本地化描述。使用协议扩展我们可以让这个实现成为默认的实现。 extension ErrorHandler { func handle(error: Error) { print(error.localizedDescription) } } 通过提供一个默认的实现,这样使得 使用默认的行为来扩展已经存在的协议的能力相当强大,这使得协议可以发展和扩展,而不需要担心破坏既有代码的兼容性。 条件扩展我们已经提供了 当错误处理器是一个视图控制器时,我们可能更倾向通过本地化描述来显示某些排序好的警告视图给他们看。为了做到这一点,可以扩展 Swift允使我们使用 extension ErrorHandler where Self: UIViewController { func handle(error: Error) { let alert = UIAlertController(title: nil,message: error.localizedDescription,preferredStyle: .alert) let action = UIAlertAction(title: "OK",style: .cancel,handler: nil) alert.addAction(action) present(alert,animated: true,completion: nil) } } 在上面代码片段里的Self(“S”大写)是指类型(结构、类或枚举)。通过这样指定后我们只能为继承于 现在, 任何符合 不明确的方法实现假设这里有两个协议,两个都有一个方法,并且签名一样。 protocol P1 { func method() //some other methods } protocol P2 { func method() //some other methods } 这两个协议都扩展了这个方法的默认实现。 extension P1 { func method() { print("Method P1") } } extension P2 { func method() { print("Method P2") } } 假设这里有一个类型,符合这两个协议。 struct S: P1,P2 { } 在这里,我们遇到了一个问题:不明确的方法实现。此类型没有清楚指明它应该使用哪个方法的实现。结果,我们得到了一个编译错误。为了修复这点,我们需要添加这个方法的实现到此类型里。 struct S: P1,P2 { func method() { print("Method S") } } 许多面向对象编程语言困扰于围绕歧义扩展定义解决方案的限制。通过允使程序员在编译器快速失败时取得控制权,Swift相当优雅地处理了这一点。 添加新方法再来看多一眼 protocol Queue { associatedtype ItemType var count: Int { get } func push(_ element: ItemType) func pop() -> ItemType } 每个符合此 extension Queue { func compare<Q>(queue: Q) -> ComparisonResult where Q: Queue { if count < queue.count { return .orderedDescending } if count > queue.count { return .orderedAscending } return .orderedSame } }
所以它不是协议方法的默认实现,而是一个新的“装饰”全部符合 协议扩展 vs 基类协议扩展和使用基类可能看起来相当相似,但使用协议扩展有几点好处。包括但不限于:
Swift标准库扩展除了扩展自己的协议,还可以扩展来自Swift标准库的协议。例如,如果想找到队列集合里的平均值,可以通过扩展标准的 由Swfit标准库提供的序列化数据结构,其元素可以通过索引下标进行遍历和访问,通常符合
extension Collection where Iterator.Element: Queue { func avgSize() -> Int { let size = map { $0.count }.reduce(0,+) return Int(round(Double(size) / Double(count.toIntMax()))) } } 现在可以统计任何队列集合( 在Swfit标准库里,协议扩展用于实现,例如,这样的方法诸如: extension Collection { public func map<T>(_ transform: (Self.Iterator.Element) throws -> T) rethrows -> [T] { } } 协议扩展和多态正如我曾经说过的,协议扩展使得我们可以为某些方法添加默认实现,也可以添加新的方法实现。但这两种特性有什么区别呢?让我们回到前面的错误处理器,找出答案。 protocol ErrorHandler { func handle(error: Error) } extension ErrorHandler { func handle(error: Error) { print(error.localizedDescription) } } struct Handler: ErrorHandler { func handle(error: Error) { fatalError("Unexpected error occurred") } } enum ApplicationError: Error { case other } let handler: Handler = Handler() handler.handle(error: ApplicationError.other) 结果是一个致命错误。 现在删除声明在协议里的 protocol ErrorHandler { } 结果还是一样:一个致命错误。 这是不是意味着,为协议方法添加默认实现和为协议添加新的方法实现,没有什么不同? 不!还是有区别的,如果把 let handler: ErrorHandler = Handler() 现在输出到控制台的是:The operation couldn’t be completed. (ApplicationError error 0.) 但如果把 protocol ErrorHandler { func handle(error: Error) } 一起来看下在各个场景中依次发生了什么。 当协议存在方法声明时: 当协议不存在方法声明时: 如果变量的类型是 面向协议的代码:安全而富有表现力在这篇文章中,我们演示了一些在Swift里协议扩展的强大之处。 不像其他使用接口的编程语言,Swift没有用不必要的限制来约束协议。Swift通过允许开发人员根据需要解决歧义来解决这些编程语言常见的问题。 使用Swift协议和协议扩展,可以编写出像大部分动态编程语言富有表现力且在编译时类型安全的代码。这使得你可以确保代码的可重用性和可维护性,以及在对Swift应用代码库做出修改时更有自信。 我们希望这篇文章对你有所帮助,同时也欢迎评论、留言、反馈或者进一步的见解。
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |