Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Swiftにおけるポリモーフィズム実現手段の検討

 Swiftにおけるポリモーフィズム実現手段の検討

2023年05月10日にオンラインで開催されたYUMEMI.grow Mobile #3で発表した資料です。

Swiftでのポリモーフィズムを実現するときによく使われる方法であるClassとProcotolにはそれぞれどのような特徴があるのか、実際にユースケースをモデリングし、そのコードを提示しながらそれぞれのメリット・デメリットについて検討いたしました。

GO Inc. dev

May 11, 2023
Tweet

More Decks by GO Inc. dev

Other Decks in Programming

Transcript

  1. © GO Inc. 2 自己紹介 プロフィール写真 GO株式会社 ユーザーシステムグループ / 髙橋秀宗

    タクシーアプリ『GO』のiOSアプリ開発を担当 趣味はギターとカメラ @h1d3mun3
  2. Index © GO Inc. 1. ポリモーフィズムを実現する手段について 2. ユースケースの定義 3. Class

    a. Classの実装 b. Classの特徴 4. Protocol a. Protocolの実装 b. Protocolの特徴 5. まとめ 6. 参考資料 3
  3. © GO Inc. 動物における「食べる」動作 人 → 肉を、口で食べる 牛 → 草を、丸呑みして胃ですりつぶして食べる

    → 「食べる」という動作は動物全般に存在するが、その詳細は各動物(≒型)によって異なる 6 例えば...
  4. © GO Inc. class Ticket { init() { } //

    発行するための接点を定義 } class ValidationRequiredTicket: Ticket { } // 検証が必要なTicketを表現 // 学割チケットを検証が必要なTicketとして表現 class StudentDiscountTicket: ValidationRequiredTicket { } チケットの表現 12
  5. © GO Inc. // 申請に必要なドキュメントの表現 class ApplicationDocument { // 自身が有効化を判定するisValidという関数を持つ

    func isValid() -> Bool { // 親クラス視点ではどのように判定してよいかわからないのでfatalErrorにする fatalError("must represent subclass!") } } // 学生証を表現 class StudentCard: ApplicationDocument { override func isValid() -> Bool { } // 学生証が有効かを判定する } 学生証の表現 13
  6. © GO Inc. // チケット申込書を表現 class TicketRequest { // チケットの発行を行うissueという関数を持つ

    func issue() throws -> Ticket { // 親クラス視点ではどのチケットを発行してよいかわからないのでfatalErrorにする fatalError("must represent subclass") } } // TicketRequestでTicketを発行できなかったときのエラー enum TicketRequestIssueError: Error { case validationFailure } 申請書の表現 - 1 14
  7. © GO Inc. // 検証が必要なTicketを発行するTicketRequestを表現 class ValidationRequiredTicketRequest: TicketRequest { let

    document: ApplicationDocument init(document: ApplicationDocument) { self.document = document } } 申請書の表現 - 2 15
  8. © GO Inc. // 学割TicketのTicketRequest class StudentDiscountTicketRequest: ValidationRequiredTicketRequest { override

    func issue() throws -> Ticket { guard document.isValid() else { throw TicketRequestIssueError.validationFailure } // 子クラス視点では型がわかるにもかかわらず、Ticket型として返却せざるをえない return StudentDiscountTicket() } } 申請書の表現 - 3 16
  9. © GO Inc. • 親クラスでは子クラス側でoverrideされていることを前提とした実装を強制される ◦ サブクラス側でのoverrideを強制できない • 子クラスは1つの親クラスしか選択できない ◦

    子クラスは親クラスに追加された変更をすべて受け入れなくてはならない • 子クラスは親クラスで宣言されたメソッド宣言を変更できないので、具体的な型情報が消 滅する ◦ Genericsを利用することで少し安全に実装することが出来ますが、書きやすさ・読みやすさに影 響するので、あまり良い解決策とは言えない デメリット 19
  10. © GO Inc. overrideについて実際のコードで見ると... 20 class TicketCounter<IssueTicket> { var count

    = 0 func issue() -> IssueTicket { // チケットを発行して、発行したカウントを記録する関数 increment() fatalError("must inprement subclass!") // どのチケットを発行するべきかはわからないので fatalに落とす } func increment() { count += 1 } // 発行したカウントを incrementする関数 } class StudentTicketCounter: TicketCounter<StudentDiscountTicket> { override func issue() -> StudentDiscountTicket { // increment関数を呼ぶ必要があることは、親クラスを見に行かないとわからない return StudentDiscountTicket() } override func increment() { } // overrideしていいのか判断がつかない }
  11. © GO Inc. class TicketCounter { // 親クラス視点ではどのチケットを発行していいか決定できないないので、Ticket型を返却させるしかない func issue()

    -> Ticket { fatalError("must implement subclass") } } class StudentTicketCounter: TicketCounter { // きちんとStudentDiscountTicketを返したのに、呼び出し元からはTicketとしか認識できない override func issue() -> Ticket { return StudentDiscountTicket() } } 型情報消失を実際のコードで見ると... 21
  12. © GO Inc. // Genericsを使うことで型情報が明確になった class TicketCounter<IssuedTicket, Document> { var

    document: Document init(document: Document) { self.document = document } func issue() -> IssuedTicket { fatalError("must inprement subclass!") } } // その代わり、記述が若干長くなる class StudentTicketCounter: TicketCounter<StudentDiscountTicket, StudentCard> { override func issue() -> StudentDiscountTicket { return StudentDiscountTicket() // 呼び出し側には StudentDiscountTicket型として返却できる } } 型情報表現について、実際のコードで見ると... 22
  13. © GO Inc. 25 protocol Ticket { static func issue()

    -> Self // 自分自身を発行するissueという接点を持つ } protocol ValidationRequiredTicket: Ticket { } // 検証が必要なTicketを表現 // 学割チケットを検証が必要なTicketとして表現 struct StudentDiscountTicket: ValidationRequiredTicket { static func issue() -> StudentDiscountTicket { return StudentDiscountTicket() } } チケットの表現
  14. © GO Inc. // 申請に必要なドキュメントの表現 protocol ApplicationDocument { // 自身が有効かを判定するisValidという関数を持つ

    func isValid() -> Bool } struct StudentCard: ApplicationDocument { func isValid() -> Bool { } // 学生証が有効かを判定する } 26 学生証の表現
  15. © GO Inc. 27 申請書の表現 - 1 protocol TicketRequest {

    // 発行したいTicketをIssueTicketTypeという型で持つ associatedtype IssueTicketType: Ticket // チケットの発行を行うissueという関数を持つ func issue() throws -> IssueTicketType } // TicketRequestでTicketを発行できなかったときのエラー enum TicketRequestIssueError: Error { case validationFailure }
  16. © GO Inc. // 検証が必要なTicketを発行するTicketRequestを表現 protocol ValidationRequiredTicketRequest: TicketRequest { //

    申請に必要なドキュメントをDocumentという型で持つ associatedtype Document: ApplicationDocument var document: Document { get } } 28 申請書の表現 - 2
  17. © GO Inc. 29 // 学割チケットのTicketRequest struct StudentDiscountTicketRequest: ValidationRequiredTicketRequest {

    // IssueTicketTypeがStudentDiscountTicket型と宣言 typealias IssueTicketType = StudentDiscountTicket // DocumentはStudentCard型と宣言 typealias Document = StudentCard let document: Document func issue() throws -> StudentDiscountTicket { guard document.isValid() else { throw TicketRequestIssueError.validationFailure } return IssueTicketType.issue() } } 申請書の表現 - 3
  18. © GO Inc. // 学割チケットのTicketRequest struct StudentDiscountTicketRequest: ValidationRequiredTicketRequest { //

    IssueTicketTypeがStudentDiscountTicket型と宣言 typealias IssueTicketType = StudentDiscountTicket // DocumentはStudentCard型と宣言 typealias Document = StudentCard let document: Document func issue() throws -> StudentDiscountTicket { guard document.isValid() else { throw TicketRequestIssueError.validationFailure } return IssueTicketType.issue() } } 型情報表現について、実際のコードで見ると... 32
  19. © GO Inc. まとめ - Class • 仕組み上Classは、振る舞いと実装を分離できない ◦ 振る舞い:

    何をしたいのか ◦ 実装: 具体的にどうやるのか • この問題は振る舞いを中心に開発する際に大きな問題となる • その一方、大きく実装を変更する必要がないものにおいては、実装が必ずついてくるとい うClassの特徴はメリットともなりうる 35
  20. © GO Inc. 39 • Protocol-Oriented Programming in Swift (WWDC15)

    • Embrace Swift Generics (WWDC22) 参考資料