概要
Swiftのプロトコル指向プログラミングのベストプラクティスがわからなかったので、海外で販売されている本を読みながらXcodeのplaygroundで挙動を見ながら勉強していきました。
これはその備忘録です。
Introducing protocol extensions (protocol extensionの導入について)
アプリ開発でよく見るプロトコル拡張の使い方はUIColor
やString
といった既存の型に新しいメソッドを追加するやり方ではないでしょうか。
extension String {
func shout(){
print(uppercased())
}
}
"Protocol extension is pretty cool".shout() // PROTOCOL EXTENSION IS PRETTY COOL といった感じに大文字に変換される
こんな感じですね。
それに対して下のコードはプロトコルに新しいインターフェースを定義してそれをextension
で実装していく流れになります。
protocol TeamRecord {
var wins: Int { get } // 勝ち数
var losses: Int { get } // 負け数
var winningPercentage: Double { get } // 勝率
}
extension TeamRecord {
var gamesPlayed: Int { // 試合数
return wins + losses
}
}
struct BaseballRecord: TeamRecord {
var wins: Int
var losses: Int
var winningPercentage: Double {
return Double(wins) / Double(wins + losses)
}
}
let bayStars = BaseballRecord(wins: 10, losses: 5)
print(bayStars.gamesPlayed) // 15 回
print(bayStars.winningPercentage) // 0.666666 ~
Default Implementations (デフォルト実装)
// Before extensionで拡張前
struct BasketBallRecord: TeamRecord {
var wins: Int
var losses: Int
let seasonLength = 82 // デフォルト実装が持てる
var winningPercentage: Double {
return Double(wins) / Double(wins + losses)
}
}
extensionで拡張させることで
extension TeamRecord {
var winningPercentage: Double {
return Double(wins) / Double(wins + losses)
}
}
// After extensionで拡張後
struct BasketBallRecord: TeamRecord {
var wins: Int
var losses: Int
let seasonLength = 82 // シーズンの長さ
}
let minneapolisFunctors = BasketBallRecord(wins: 60, losses: 22)
print(minneapolisFunctors.winningPercentage) // winningPercentage が使えるようになる // 0.7317
とstruct
の中の実装は少なくなるよね、といった話し。
さらにstruct
のインスタンスはprotocol extensionで実装したものが使えるようになっています。
struct HockeyRecord: TeamRecord {
var wins: Int
var losses: Int
var ties: Int // 引き分けのプロパティ、 追加
// Hockey のレコードは引き分けのプロパティを導入したので「勝率」の計算方法が変わる
var winningPercentage: Double {
return Double(wins) / Double(wins + losses + ties)
}
}
let chicagoOptionals = BasketBallRecord(wins: 10, losses: 6)
let phoenixStridables = HockeyRecord(wins: 8, losses: 7, ties: 1)
print(chicagoOptionals.winningPercentage) // 10 / (10 + 6) = 0.625
print(phoenixStridables.winningPercentage) // 8 / (8 + 7 + 1) = 0.5
Understanding protocol extension dispatching
protocol WinLoss {
var wins: Int { get }
var losses: Int { get }
}
extension WinLoss {
var winningPercentage: Double {
return Double(wins) / Double(wins + losses)
}
}
struct CricketRecord: WinLoss {
var wins: Int
var losses: Int
var draws: Int
var winningPercentage: Double {
return Double(wins) / Double(wins + losses + draws)
}
}
let miamiTuples = CricketRecord(wins: 8, losses: 7, draws: 1)
let winLoss: WinLoss = miamiTuples
print(miamiTuples.winningPercentage) // 0.5
print(winLoss.winningPercentage) // 0.53 drawsがカウントされないため
WinLoss
には引き分けの draws
が存在しないので、winLoss
はprotocol-extensionの方のメソッドwinningPercentage
を出力する
Type constraints
// 概念
protocol PostSeasonEligible {
var minimumWinsForPlayoffs: Int { get }
}
// PostSeasonEligible と TeamRecord に準拠している時だけ適用される
extension TeamRecord where Self: PostSeasonEligible {
var isPlayoffEligible: Bool {
return wins > minimumWinsForPlayoffs
}
}
// 具体例
protocol Tieable {
var ties: Int { get }
}
extension TeamRecord where Self : Tieable {
var winningPercentage: Double {
return Double(wins) / Double(wins + losses + ties)
}
}
struct RugbyRecord: TeamRecord, Tieable {
var wins: Int
var losses: Int
var ties: Int
}
//struct HockeyRecord: TeamRecord {
// var wins: Int
// var losses: Int
// var ties: Int
//
// var winningPercentage: Double {
// return Double(wins) / Double(wins + losses + ties)
// }
//}
let rugbyRecord = RugbyRecord(wins: 8, losses: 7, ties: 1)
print(rugbyRecord.winningPercentage) // 0.5
Protocol-oriented benefits (プロトコル指向のメリット)
プロトコル指向のプログラミングをやるメリットとして
- Programming to interfaces, not implementations
- Traits, mixins and multiple inheritance
- Simplicity
のメリットを享受出来る。
Programming to interfaces, not implementations
プロトコルは実装にフォーカスするのではなくインターフェースにフォーカスする
protocol にinterfaceを持たせるのでprotocolを見ればどういった機能なのかわかるので最終的にはFatClassが無くなりそうだよね。
Traits, mixins and multiple inheritance
多重継承 の問題を解決する
interfaceをprotocolに持たせてextensionで実装を行い、それをstruct やclass に準拠させるのでクラスの共通実装が必要なくなる。そのため、BaseClassといった共通クラスを継承したサブクラスにする必要がないので A is B 問題を解決する糸口になる。
Simplicity
単純かつ、簡潔、簡易なものにする
あとがき
海外のプログラミング本は最初はクオリティとか本当に理解できるのかと疑ってましたが、日本で販売されているプログラミング本よりも大変わかりやすかったです。最初は英語の苦手意識がありました。ですが、それを乗り越えると英語の解説書の方が回りくどい表現がない分理解が早くなることがわかる。
delegateとかprotocolなどの専門用語を日本語に翻訳する方が難しい気もします。
英語と聞くとアレルギーを起こすエンジニアさんも多数いるとは思いますが、
海外の本は日本の受験英語みたいに難しい英文はそこまでないように思います。
だったら最初から英語の本を読みながら英語でプログラミングを理解する方がいい気がします。
ですが、これは個々人の好みの問題ではあるところだと思います。