在 Swift 結構體中使用 Mutating 函數的最佳時機

jwob6384 9年前發布 | 8K 次閱讀 Swift 函數式編程 Apple Swift開發

來自: 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。

 本文由用戶 jwob6384 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!