Видеокурсы по изучению языка программирования Swift. Подробнее

Делегирование

Если вы нашли опечатку в тексте, выделите ее и нажмите CTRL + ENTER.

Делегирование - это шаблон, который позволяет классу или структуре передавать (или делегировать) некоторую ответственность экземпляру другого типа. Этот шаблон реализуется определением протокола, который инкапсулирует делегируемые полномочия, таким образом, что соответствующий протоколу тип (делегат) гарантировано получит функциональность, которая была ему делегирована. Делегирование может быть использовано для ответа на конкретное действие или для получения данных из внешнего источника без необходимости знания типа источника.

Пример ниже определяет два протокола для использования в играх, основанных на бросках игральных костей:

protocol DiceGame {
    var dice: Dice { get }
    func play()
}
protocol DiceGameDelegate {
    func gameDidStart(_ game: DiceGame)
    func game(_ game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int)
    func gameDidEnd(_ game: DiceGame)
}

Протокол DiceGame является протоколом, который может быть принят любой игрой, которая включает игральную кость. Протокол DiceGameDelegate может быть принят любым типом для отслеживания прогресса DiceGame.

Вот версия игры “Змеи и лестницы”, которая первоначально была представлена в разделе Управление потоком. Эта версия адаптирована под использование экземпляра Dice для своих бросков кости, для соответствия протоколу DiceGame и для уведомления DiceGameDelegate о прогрессе:

class SnakesAndLadders: DiceGame {
    let finalSquare = 25
    let dice = Dice(sides: 6, generator: LinearCongruentialGenerator())
    var square = 0
    var board: [Int]
    init() {
        board = Array(repeating: 0, count: finalSquare + 1)
        board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02
        board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08
    }
    var delegate: DiceGameDelegate?
    func play() {
        square = 0
        delegate?.gameDidStart(self)
        gameLoop: while square != finalSquare {
            let diceRoll = dice.roll()
            delegate?.game(self, didStartNewTurnWithDiceRoll: diceRoll)
            switch square + diceRoll {
            case finalSquare:
                break gameLoop
            case let newSquare where newSquare > finalSquare:
                continue gameLoop
            default:
                square += diceRoll
                square += board[square]
            }
        }
        delegate?.gameDidEnd(self)
    }
}

Для описания процесса игры “Змеи и лесницы”, посмотрите раздел инструкции Break.

Эта версия игры обернута в класс SnakesAndLadders, который принимает протокол DiceGame. Он предоставляет свойство dice и метод play() для соответствия протоколу. (Свойство dice объявлено как константное свойство, потому что оно не нуждается в изменении значения после инициализации, а протокол требует только чтобы оно было доступным.)

Настройка игры “Змеи и лестницы” происходит в инициализаторе класса init(). Все логика игры перемещается в метод play протокола, который использует требуемое свойство протокола для предоставления значений броска игральной кости.

Обратите внимание, что свойство delegate определено как опциональное DiceGameDelegate, потому что делегат не требуется для игры. Так как оно является опциональным типом, свойство delegate автоматически устанавливает начальное значение равное nil. Таким образом, у каждого экземпляра игры есть установки свойства подходящему делегату.

DiceGameDelegate предоставляет три метода для отслеживания прогресса игры. Эти три метода были включены в логику игры внутри метода play, и вызываются когда начинается новая игра, начинается новый ход или игра кончается.

Так как свойство delegate является опциональным DiceGameDelegate, метод play() использует опциональную последовательность каждый раз, как вызывается этот метод у делегата. Если свойство delegate равно nil, то этот вызов этого метода делегатом проваливается без возникновения ошибки. Если свойство delegate не nil, вызываются методы делегата, которые передаются в экземпляр SnakesAndLadders в качестве параметра.

Следующий пример показывает класс DiceGameTracker, который принимает протокол DiceGameDelegate:

class DiceGameTracker: DiceGameDelegate {
    var numberOfTurns = 0
    func gameDidStart(_ game: DiceGame) {
        numberOfTurns = 0
        if game is SnakesAndLadders {
            print("Начали новую игру Змеи и лестницы")
        }
        print("У игральной кости \(game.dice.sides) граней")
    }
    func game(_ game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int) {
        numberOfTurns += 1
        print("Выкинули \(diceRoll)")
    }
    func gameDidEnd(_ game: DiceGame) {
        print("Длительность игры \(numberOfTurns) хода")
    }
}

DiceGameTracker реализует все три метода, которые требует DiceGameDelegate. Он использует эти методы для отслеживания количества ходов, которые были сделаны в игре. Он сбрасывает значение свойства numberOfTurns на ноль, когда начинается игра и увеличивает каждый раз, как начинается новый ход и выводит общее число ходов, как только кончается игра.

Реализация gameDidStart(_:), показанная ранее, использует параметр game для отображения вступительной информации об игре, в которую будут играть. Параметр game имеет тип DiceGame, но не SnakesAndLadders, так что gameDidStart(_:) может получить и использовать только те методы и свойства, которые реализованы как часть протокола DiceGame. Однако метод все еще может использовать приведение типов для обращения к типу основного (исходного) экземпляра. В этом примере, он проверяет действительно ли game является экземпляром SnakesAndLadders или нет, а потом выводит соответствующее сообщение.

gameDidStart(_:) так же получает доступ к свойству dice, передаваемого параметра game. Так как известно, что game соответствует протоколу DiceGame, то это гарантирует наличие свойства dice, таким образом метод gameDidStart(_:) может получить доступ и вывести сообщение о свойстве кости sides, независимо от типа игры, в которую играют.

Теперь давайте взглянем на то, как выглядит DiceGameTracker в действии:

let tracker = DiceGameTracker()
let game = SnakesAndLadders()
game.delegate = tracker
game.play()
// Начали новую игру Змеи и лестницы
// У игральной кости 6 граней
// Выкинули 3
// Выкинули 5
// Выкинули 4
// Выкинули 5
// Длительность игры 4 хода
Swift: 
3.0