协议(Protocols)
我已经提及了structs
和enums
之间的相似性。除了附加方法的能力之外,Swift也允许你在枚举中使用协议(Protocols)和协议扩展(Protocol Extension)。
Swift
协议定义一个接口或类型以供其他数据结构来遵循。enum
当然也不例外。我们先从Swift
标准库中的一个例子开始.
CustomStringConvertible
是一个以打印为目的的自定义格式化输出的类型。
protocol CustomStringConvertible {
var description: String { get }
}
该协议只有一个要求,即一个只读(getter
)类型的字符串(String
类型)。我们可以很容易为enum
实现这个协议。
一些协议的实现可能需要根据内部状态来相应处理要求。例如定义一个管理银行账号的协议。
protocol AccountCompatible {
var remainingFunds: Int { get }
mutating func addFunds(amount: Int) throws
mutating func removeFunds(amount: Int) throws
}
你也许会简单地拿struct
实现这个协议,但是考虑应用的上下文,enum
是一个更明智的处理方法。不过你无法添加一个存储属性到enum
中,就像var remainingFuns:Int
。那么你会如何构造呢?答案灰常简单,你可以使用关联值完美解决:
enum Account {
case Empty
case Funds(remaining: Int)
enum Error: ErrorType {
case Overdraft(amount: Int)
}
var remainingFunds: Int {
switch self {
case Empty: return 0
case Funds(let remaining): return remaining
}
}
}
为了保持代码清爽,我们可以在enum
的协议扩展(protocl extension
)中定义必须的协议函数:
extension Account: AccountCompatible {
mutating func addFunds(amount: Int) throws {
var newAmount = amount
if case let .Funds(remaining) = self {
newAmount += remaining
}
if newAmount < 0 {
throw Error.Overdraft(amount: -newAmount)
} else if newAmount == 0 {
self = .Empty
} else {
self = .Funds(remaining: newAmount)
}
}
mutating func removeFunds(amount: Int) throws {
try self.addFunds(amount * -1)
}
}
var account = Account.Funds(remaining: 20)
print("add: ", try? account.addFunds(10))
print ("remove 1: ", try? account.removeFunds(15))
print ("remove 2: ", try? account.removeFunds(55))
// prints:
// : add: Optional(())
// : remove 1: Optional(())
// : remove 2: nil
正如你所看见的,我们通过将值存储到enum cases
中实现了协议所有要求项。如此做法还有一个妙不可言的地方:现在整个代码基础上你只需要一个模式匹配就能测试空账号输入的情况。你不需要关心剩余资金是否等于零。