Skip to content

Swift_protocol

kobayashiharuto edited this page Aug 1, 2021 · 7 revisions

プロトコル

設計図を書くルールを決めよう!

知ってる人向け:他の言語で言うインターフェースに近い存在です


プロトコルってそもそもどういう意味?

一般的にプロトコルというのは、データ通信を行うルールの事です。
ウェブ通信で使われる HTTP の P はプロトコルを意味しています。

なぜそんなルールがあるのでしょうか?
それは、いろいろな企業が勝手に通信機械を作っても、異なる通信機械間での通信を可能にするためです。

例えば、メールの送信ルールを以下のようにしましょう。

[SENDER]: "[ここに送信者を書く]"
[RECEIVER]: "[ここに受信者を書く]"
[MESSAGE]: "[ここに本文を書く]"

すると以下のような情報にすればメール通信が可能になるわけですね。

[SENDER]: "小林"
[RECEIVER]: "佐藤"
[MESSAGE]: "こんにちは!"

これで、どの企業も企業間で話し合うことなく、送信者の名前を見るには SENDER を見たらいいんだね、ってわかるわけです。
もしルールが決まって無ければ、SENDER が sender だったり、MESSAGE が BODY になったりと...個々が自由に作ってしまい、どれ見ればいいんだ!ってなってしまいますね...
そうなれば異なる企業間では通信ができなくなっていたことでしょう。

つまり、共通ルールであるプロトコルを作り、それにみんなが従うことで、わざわざ話し合いなどせずとも問題なく通信ができるようになったわけです。


問題のコード

さて、そんな便利なプロトコル。実は Swift にも存在しています。
とはいえ、別に通信のために存在してるわけではありません。
でもさっき言った、話し合わなくとも問題なく通信ができるってとこ、プログラミングでも便利だと思いませんか?

例えば、あなたはとあるソシャゲの開発者チームのひとり、 A さんです。
ここで、課金ユーザーと無課金ユーザーのクラスを作るとしましょう。
こいつらには、課金度に代わってコイン消費量とレア度が変わるクソみたいなガチャを実装します。

// ユーザークラス
class User {
    let name: String
    
    init(name: String) {
        self.name = name
    }
}

// 無課金ユーザー
class NoMoneyUser: User {
    let cost = 10
    
    func gacha() {
        print("\(self.cost)コイン使用してガチャを引きます!")
        print("レア!")
    }
}

// 課金ユーザークラス
class MoneyUser: User {
    let cost = 5
    
    func gacha() {
        print("\(self.cost)コイン使用してガチャを引きます!")
        print("激レア!")
    }
}

さて、このとき新しく重課金ユーザークラスを B さんが実装したようです。

// 重課金ユーザークラス
class HeavyMoneyUser: User {
    let coin = 3
    
    func gasha() {
        print("\(self.coin)使用してガチャを引きます!")
        print("超激レア!")
    }
}

あらら、なんとスペルをミスってしまったようです。しかも costcoin になっています...
しかし...もちろんこれでコンパイルエラーは出てくれません。バグる gasha メソッドが実装されてしまいました。


A さん「じゃあ親クラスに gacha メソッドを実装して override して使わせればいいんじゃね?」
いいですね。やってみましょう。

// ユーザークラス
class User {
    let name: String
    
    init(name: String) {
        self.name = name
    }
    
    func gacha() {
        print("")
    }
}

// 無課金ユーザー
class NoMoneyUser: User {
    let cost = 10
    
    override func gacha() {
        print("\(self.cost)使用してガチャを引きます!")
        print("レア!")
    }
}

// 課金ユーザークラス
class MoneyUser: User {
    let cost = 5
    
    override func gacha() {
        print("\(self.cost)使用してガチャを引きます!")
        print("激レア!")
    }
}


// 重課金ユーザークラス
class HeavyMoneyUser: User {
    let coin = 3
    
    func gasha() {
        print("\(self.coin)使用してガチャを引きます!")
        print("超激レア!")
    }
}

あらら、ちゃんと B さんに親クラスの gacha メソッドを override してくれって話しましたか?
ちゃんと話さないと、override してくれませんよ。

バグってしまいます...話し合いましょう...でも...いちいち仕様が変るたびに...話し合い...?
そんなんめんどいわ!


クラスの仕様を決めてやれ

みんなで作ってても、話し合うことなくルールに則った実装ができれば安全ですね。
ではそれはどうすれば実現できるでしょうか?

答えは、 実装すべきメソッドを実装してなかったらコンパイルエラーで教えてくれ! です。
これをプロトコルで実現することができます。
さあ、プロトコルを使ってクラスを作るルールを決めてやりましょう。


User クラスではなく、GachaNeed プロトコルを作成しました。
ここに書いてあるメソッドやプロパティを実装しなかったらコンパイルエラーで教えてくれます。

protocol GachaNeed {
    var cost: Int { get }
    func gacha() -> void
}

プロパティに { get } だのがついてますが、これは読み取りのみを可能にするものです。アクセス修飾子のときに話した private(set) と一緒ですね。
これは継承と同じように使うことができます。

// 無課金ユーザー
class NoMoneyUser: GachaNeed {
    let name: String
    let cost = 10
    
    init(name: String) {
        self.name = name
    }
    
    override func gacha() {
        print("\(self.cost)使用してガチャを引きます!")
        print("レア!")
    }
}

// 課金ユーザークラス
class MoneyUser: GachaNeed {
    let name: String
    let cost = 5
    
    init(name: String) {
        self.name = name
    }
    
    override func gacha() {
        print("\(self.cost)使用してガチャを引きます!")
        print("激レア!")
    }
}


// 重課金ユーザークラス
class HeavyMoneyUser: GachaNeed {
    let name: String
    let coin = 3	// coinになっているのでエラーが出る
    
    init(name: String) {
        self.name = name
    }
    
	// gashaになってるのでエラーが出る!
    func gasha() {
        print("\(self.coin)使用してガチャを引きます!")
        print("超激レア!")
    }
}

これで B さんはルールに則っていないメソッドに気づくことができます!

一切話し合いの必要はありません!なんて便利なんだ!いやでも待ってくれ!

User クラスの継承を止めたので、いちいち name プロパティに書き込むイニシャライザを書かなければならない...
安心してください。継承 + プロトコルを組み合わせられます。

// ユーザークラス
class User {
    let name: String
    
    init(name: String) {
        self.name = name
    }
}

// 無課金ユーザー
class NoMoneyUser: User, GachaNeed {
    let cost = 10
    
    override func gacha() {
        print("\(self.cost)使用してガチャを引きます!")
        print("レア!")
    }
}

// 課金ユーザークラス
class MoneyUser: User, GachaNeed {
    let cost = 5
    
    override func gacha() {
        print("\(self.cost)使用してガチャを引きます!")
        print("激レア!")
    }
}


// 重課金ユーザークラス
class HeavyMoneyUser: GachaNeed {
    let coin = 3	// coinになっているのでコンパイルエラーが出る
    
	// gashaになってるのでコンパイルエラーが出る!
    func gasha() {
        print("\(self.coin)使用してガチャを引きます!")
        print("超激レア!")
    }
}

完璧ですね。ルールに則っているうえ、うまく使いまわせた美しいコードになりました!


まとめ

  • プロトコルはクラスの仕様書、ルール
  • 仕様に則っていなけば、コンパイルエラーで教えてくれる
  • 他のクラスの継承と合わせて使える
Clone this wiki locally