
Swiftで行こう! -- Delegation
Delegation、日本語で委譲を意味します。
冷蔵庫を考えてみます。冷蔵庫には冷蔵、冷凍の機能が備わってます。
冷蔵庫ですが使う人により必要な機能、いらない機能があります。
この機能についてを切り離して実装できると言うのが委譲の仕組みです。
冷蔵の機能としては冷やすという機能について通常の冷蔵(4℃)、チルド(0℃)、急速冷蔵、などが考えられます。
実装としては機能については通常の冷蔵で良いのであれば実装するときには冷蔵のみを組み込んでやればOKです。
簡単に言葉で表すと、
ある機能について、本体以外で準備し、その機能を本体に組むこむことで、実施できるようにします。
なかなかわかりにくいですかね。アップルのマニュアルではDiceゲームを題材にして説明してあります。
その機能としては、まずランダムを生成するprotocol
protocol RandomNumberGenerator {
func random() -> Double
}
これを元にclassに作ります。線形合同法生成器として知られている擬似乱数生成器アルゴリズムです。
class LinearCongruentialGenerator: RandomNumberGenerator {
var lastRandom = 42.0
let m = 139968.0
let a = 3877.0
let c = 29573.0
func random() -> Double {
lastRandom = ((lastRandom * a + c).truncatingRemainder(dividingBy:m))
return lastRandom / m
}
}
実際のDiceを組んで行きます。
class Dice {
let sides: Int
let generator: RandomNumberGenerator
init(sides: Int, generator: RandomNumberGenerator) {
self.sides = sides
self.generator = generator
}
func roll() -> Int {
return Int(generator.random() * Double(sides)) + 1
}
}
Diceは、generatorで乱数発生できて、sidesで乱数の範囲を決めて乱数を表示していきます。
次に、ゲームとして機能するようにしていきます。まずゲーム全体の構成を考えていきます。protocolです。
protocol DiceGame {
var dice: Dice { get }
func play()
}
次に、ゲームのなりふり、スタートして終わるまでの機能のprotocolです。
protocol DiceGameDelegate: AnyObject {
func gameDidStart(_ game: DiceGame)
func game(_ game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int)
func gameDidEnd(_ game: DiceGame)
}
Game本体をprotocolに準拠した形で、実際に作っていきます。
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
}
weak 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)
}
}
このclassのなかで
weak var delegate: DiceGameDelegate?
の部分これが肝です。
この部分のために以下のprotocol、DiceGameDelegateに準拠したclassを作ります。
class DiceGameTracker: DiceGameDelegate {
var numberOfTurns = 0
func gameDidStart(_ game: DiceGame) {
numberOfTurns = 0
if game is SnakesAndLadders {
print("Started a new game of Snakes and Ladders")
}
print("The game is using a \(game.dice.sides)-sided dice")
}
func game(_ game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int) {
numberOfTurns += 1
print("Rolled a \(diceRoll)")
}
func gameDidEnd(_ game: DiceGame) {
print("The game lasted for \(numberOfTurns) turns")
}
}
実際に実行してみます。
まずDelegeteのインスタンス。
let tracker = DiceGameTracker()
次にゲーム全体のインスタンス作ります。
let game = SnakesAndLadders()
次に実際にDelegate,委譲します。
game.delegate = tracker
ゲームSnakesAndLadders()のインスタンスgamaの中でDiceGameTracker()のインスタンスのtrackerを当てはめます。
そして、
game.play()
としてゲームスタートします。
Started a new game of Snakes and Ladders
The game is using a 6-sided dice
Rolled a 3
Rolled a 5
Rolled a 4
Rolled a 5
The game lasted for 4 turns
と出力され、ゲームが成立します。