Swiftでのプロトコル指向のプログラミングを考えてみる

# 概要

Swiftのプロトコル指向プログラミングのベストプラクティスがわからなかったので、海外で販売されている本を読みながらXcodeのplaygroundで挙動を見ながら勉強していきました。

これはその備忘録です。

## Introducing protocol extensions (protocol extensionの導入について)

アプリ開発でよく見るプロトコル拡張の使い方は`UIColor`や`String`といった既存の型に新しいメソッドを追加するやり方ではないでしょうか。

```ProtocolPlayground.swift
extension String {
    func shout(){
        print(uppercased())
    }
}
"Protocol extension is pretty cool".shout() // PROTOCOL EXTENSION IS PRETTY COOL といった感じに大文字に変換される
```

こんな感じですね。

それに対して下のコードはプロトコルに新しいインターフェースを定義してそれを`extension`で実装していく流れになります。

```ProtocolPlayground.swift
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 (デフォルト実装)

```ProtocolPlayground.swift

// Before extensionで拡張前

struct BasketBallRecord: TeamRecord {
    var wins: Int
    var losses: Int
    let seasonLength = 82  // デフォルト実装が持てる

    var winningPercentage: Double {
        return Double(wins) / Double(wins + losses)
    }
}
```
extensionで拡張させることで

```ProtocolPlayground.swift
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で実装したものが使えるようになっています。

```ProtocolPlayground.swift

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

```ProtocolPlayground.swift
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

```ProtocolPlayground.swift
// 概念
protocol PostSeasonEligible {
    var minimumWinsForPlayoffs: Int { get }
}

// PostSeasonEligible と TeamRecord に準拠している時だけ適用される
extension TeamRecord where Self: PostSeasonEligible {
    var isPlayoffEligible: Bool {
        return wins > minimumWinsForPlayoffs
    }
}
```

```ProtocolPlayground.swift
// 具体例
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 (プロトコル指向のメリット)

プロトコル指向のプログラミングをやるメリットとして

1. Programming to interfaces, not implementations
2. Traits, mixins and multiple inheritance
3. 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などの専門用語を日本語に翻訳する方が難しい気もします。  

英語と聞くとアレルギーを起こすエンジニアさんも多数いるとは思いますが、  
海外の本は日本の受験英語みたいに難しい英文はそこまでないように思います。  

だったら最初から英語の本を読みながら英語でプログラミングを理解する方がいい気がします。  
ですが、これは個々人の好みの問題ではあるところだと思います。