在 Swift 結構體中使用 Mutating 函數的最佳時機
來自: http://swift.gg/2016/02/06/when-to-use-mutating-functions-in-swift-structs/
作者:NatashaTheRobot, 原文鏈接 ,原文日期:2016-1-13
譯者: walkingway ;校對: Cee ;定稿: numbbbbb
我認為關于 Swift 最棒的一個特性就是:在這門語言構建的工程中可以使用大量的不可變對象。這種特性使我們的代碼更加清晰,也更加安全(如果你還對此存疑,強烈推薦觀看這篇 演講 )。
但當我們真正需要去改變數據時,又該怎么處理呢?
函數方式
舉個例子,假如有一個井字棋的游戲棋盤,我需要修改棋盤上各個點的狀態:
struct Position {
let coordinate: Coordinate
let state: State
enum State: Int {
case X, O, Empty
}
}
struct Board {
let positions: [Position]
// 需要添加一個函數來更新位置
// 從空棋盤到 X 或 O
}我們采取 函數式編程 的方式,可以很輕松地得到一個新棋盤!
struct Board {
let positionsMatrix: [[Position]]
init() {
// 初始化一個初始棋盤
}
// 函數式的實現方式
func boardWithNewPosition(position: Position) -> Board {
var positions = positionsMatrix
let row = position.coordinate.row.rawValue
let column = position.coordinate.column.rawValue
positions[row][column] = position
return Board(positionsMatrix: positions)
}
}我更喜歡函數式編程是因為這種方式沒有副作用,將變量統統改為常量,測試起來也是相當容易!
class BoardTests: XCTestCase {
func testBoardWithNewPosition() {
let board = Board()
let coordinate = Coordinate(row: .Middle, column: .Middle)
let initialPosition = board[coordinate]
XCTAssertEqual(initialPosition.state, Position.State.Empty)
let newPosition = Position(coordinate: coordinate, state: .X)
let newBoard = board.boardWithNewPosition(newPosition)
XCTAssertEqual(newBoard[coordinate], newPosition)
}
}但是,我們還有更好的解決方案!
使用 Mutating 關鍵字
讓我們來跟蹤一下每個用戶下井字棋獲勝的次數,首先創建一個計數器:
struct Counter {
let count: Int
init(count: Int = 0) {
self.count = count
}
// 需要一個方法來增加計數
}當然,我們也可以通過函數式編程的方式來實現這個計數器:
struct Counter {
let count: Int
init(count: Int = 0) {
self.count = count
}
// 函數式的實現方式
func counterByIncrementing() -> Counter {
let newCount = count + 1
return Counter(count: newCount)
}
}如果你嘗試去實現此函數,應該這樣寫:
var counter = Counter() counter = counter.counterByIncrementing()
最終你會發現相當 晦澀難懂 !所以這里我更推薦使用 mutating 關鍵字而不是函數式編程:
struct Counter {
// 現在這個 count 改為變量了 :/
var count: Int
init(count: Int = 0) {
self.count = count
}
// 使用 mutating 關鍵字來實現修改 count
mutating func increment() {
count += 1
}
}雖然我不喜歡 increment 函數中的副作用,但為了更好的可讀性,這點犧牲是值得的。
var counter = Counter() counter.increment()
讓我們再進一步,通過使用 private setter 阻止了從外部修改 count 變量,從而將潛在危險降到最低:
struct Counter {
// 將 setter 方法設為私有,
// 這樣只有 increment 函數能夠修改它!
private(set) var count: Int
init(count: Int = 0) {
self.count = count
}
// 使用 mutating 關鍵字來實現修改 count
mutating func increment() {
count += 1
}
}結論
當我面臨要選擇 mutating 關鍵字還是函數式編程時,通常我都會選擇函數式編程,但這一些都是有前提的,那就是: 不能犧牲可讀性 !
為你的接口編寫測試是一種很好的習慣,可以用來檢驗你的函數方法是否滿足預期。如果你覺得接口寫起來很怪異、不直觀,那就去換一種方式去實現吧!最后別忘了用私有 setter 設置你的內部變量哦!
本文由 SwiftGG 翻譯組翻譯,已經獲得作者翻譯授權,最新文章請訪問http://swift.gg。