Должен ли я использовать классы или структуры для элементов, хранящихся в массиве Swift

Я хотел бы изменить свойство в структуре Swift, хранящейся в массиве. Я исполнил танец переназначения, но это не кажется правильным.

Мне рекомендуется использовать Struct, где это возможно, однако этот относительно простой вариант использования (ниже) подталкивает меня к использованию классов (ссылочных типов).

Должен ли я использовать Classes для Game и/или Player?

Ниже приведен пример кода... с сопровождающим UnitTest

Сводка теста
• Создать игру
• Создать двух игроков
• Добавить обоих игроков в игру
• Отправить сообщение в игру для уменьшения размера игрока
• Игра повторяет сбор данных (players)
• Находит игрока и отправляет сообщение decrementScore
Проверка не удалась — очки игроков были не такими, как ожидалось (60 и 70 соответственно).

struct Game {
    fileprivate(set) var players = [Player]()
}

extension Game {
    mutating func addPlayer(_ player: Player) {
        players.append(player)
    }

    mutating func decrementPlayer(_ decrementPlayer: Player, byScore: Int) {
        for var player in players {
            if player == decrementPlayer {
                player.decrementScore(by: byScore)
            }
        }
    }
}


struct Player {
    var name: String
    var score: Int

    init(name: String, score: Int) {
        self.name = name
        self.score = score
    }

    mutating func decrementScore(by byScore: Int) {
        self.score -= byScore
    }
}

extension Player: Equatable {
    public static func ==(lhs: Player, rhs: Player) -> Bool {
        return lhs.name == rhs.name
    }
}

class GameTests: XCTestCase {

var sut: Game!

func testDecrementingPlayerScores_isReflectedCorrectlyInGamePlayers() {
        sut = Game()
        let player1 = Player(name: "Ross", score: 100)
        let player2 = Player(name: "Mary", score: 100)

        sut.addPlayer(player1)
        sut.addPlayer(player2)
        XCTAssertEqual(2, sut.players.count)    // Passes

        sut.decrementPlayer(player1, byScore: 40)
        sut.decrementPlayer(player2, byScore: 30)
        XCTAssertEqual(60, sut.players[0].score) // Fails - score is 100 .. expecting 60
        XCTAssertEqual(70, sut.players[1].score) // Fails - score is 100 .. expecting 70
    }
}

person MDMonty    schedule 12.02.2018    source источник
comment
Пожалуйста, добавьте также тестовый код.   -  person picciano    schedule 12.02.2018
comment
Подойдет .. спасибо @picciano Отделил XCTestCase от существующего примера кода   -  person MDMonty    schedule 12.02.2018


Ответы (3)


Мне рекомендуется использовать Struct, где это возможно.

Да, это проблематично. Вам следует рекомендовать использовать struct там, где это уместно. Вообще говоря, я считаю, что struct не всегда так уместны, как диктует мода.

Ваша проблема здесь в том, что оператор for var player ... фактически создает изменяемую копию каждого игрока по мере повторения и изменения копии. Если вы хотите придерживаться structs, вам, вероятно, потребуется принять более функциональный подход.

mutating func decrementPlayer(_ decrementPlayer: Player, byScore: Int) {
    players = players.map {
        return $0 == decrementPlayer ? $0.scoreDecrementedBy(by: byScore) : $0
    }
}

Или более традиционным (и почти наверняка более эффективным) способом было бы найти индекс нужного игрока.

mutating func decrementPlayer(_ decrementPlayer: Player, byScore: Int) {
    if let index = players.index(of: decrementPlayer)
    {
        players[index].decrementScore(by: byScore)
    }
}
person JeremyP    schedule 12.02.2018
comment
Я хотел бы принять ваш ответ .. спасибо. Хотя, подумав и посоветовав коллегам, я считаю, что метод decrementScore должен возвращать новую структуру Player. а затем выполните задание обратно в массив игроков (используя индекс, который вы описали выше) ... спасибо за ваше время и помощь. - person MDMonty; 14.02.2018
comment
@MDMonty Да, я думаю, что функциональный подход, который выглядит более умным, будет менее эффективным, поскольку он создает совершенно новый массив. Я думаю, что мой метод index будет самым быстрым, но ваш метод, вероятно, приблизится к нему после оптимизации. - person JeremyP; 14.02.2018

Я хотел бы изменить свойство в структуре Swift, хранящейся в массиве. Я исполнил танец переназначения, но это не кажется правильным.

Что ж, это то, что вам придется сделать, поскольку структуры не изменяются на месте. Если вы хотите иметь возможность изменять объект на месте (внутри массива), вам нужен класс.

person matt    schedule 12.02.2018

Вопрос, который следует задать при выборе между типами значений (структурами) и ссылочными типами (классами), заключается в том, имеет ли смысл иметь повторяющиеся экземпляры.

Например, возьмем число 5, которое является Int. Вы можете копировать его сколько угодно раз, он дешевый (как и структуры) и не создает проблем при копировании.

Теперь давайте рассмотрим FileHandle. Может это структура? Да. Должна ли это быть структура? Нет. Создание структуры означает, что дескриптор будет копироваться всякий раз, когда он передается в качестве аргумента или сохраняется как свойство. Это означает, что каждый будет иметь другую ссылку на этот дескриптор (намеренно игнорируя копирование при записи). Если один владелец ссылки решит закрыть дескриптор, это косвенно аннулирует все остальные ссылки FileHandle, что, вероятно, вызовет неожиданное поведение.

В вашем конкретном случае я бы посоветовал сделать Player классом. Я предполагаю, что игрок также будет иметь некоторые эффекты пользовательского интерфейса, поэтому имеет смысл использовать ссылочный тип. Однако, если Player используется только для статистических целей, сделайте его структурой.

person Cristik    schedule 17.02.2018