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

Protocolを利用したポリモーフィズム(Polymorphism)とそのメリット

 Protocolを利用したポリモーフィズム(Polymorphism)とそのメリット

2023年04月12日にオンラインで開催されたYUMEMI.grow Mobile #2で発表した資料です。
プログラムにおけるポリモーフィズムの考え方とそれを実現するSwiftのProtocolという機能について、なるべく皆様の実際の業務でご活用いただけるようなイメージが湧くよう、実際のコードや、いくつかの事例などを多く含めながらわかりやすさを優先して発表させていただきました。

GO Inc. dev

April 24, 2023
Tweet

More Decks by GO Inc. dev

Other Decks in Programming

Transcript

  1. © GO Inc. 7 動物における「食べる」動作 人 → 肉を、口で食べる 牛 →

    草を、丸呑みして胃ですりつぶして食べる → 「食べる」という動作は動物全般に存在するが、その詳細は各動物 (≒型)によって異なる 例えば...
  2. © GO Inc. 8 protocol Animal { associatedtype Feed //

    食べ物をFeedという型でもつ func eat(_ food: Feed) // 食べるためのeatという関数を持つ } struct Human: Animal { typealias Feed = Meat // FeedはMeat型と宣言 func eat(_ food: Feed) { } // Meat型を引数に取るeat関数を宣言 } struct Cow: Animal { typealias Feed = Hay // FeedはHay型と宣言 func eat(_ food: Hay) { } // Hay型を引数に取るeat関数を宣言 } 例えば...
  3. © GO Inc. 15 protocol Ticket { static func issue()

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

    自身が有効かを判定するisValidという関数を持つ func isValid() -> Bool } struct StudentCard: ApplicationDocument { func isValid() -> Bool { } // 学生証が有効かを判定する } 学生証の表現
  5. © GO Inc. protocol TicketRequest { // 発行したいTicketをIssueTicketTypeという型で持つ associatedtype IssueTicketType:

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

    // 申請に必要なドキュメントをDocumentという型で持つ associatedtype Document: ApplicationDocument var document: Document { get } } 申請書の表現 - 2
  7. © GO Inc. 19 // 学割チケットの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
  8. © GO Inc. 21 // 学割チケットリクエストの生成 let studentDiscountTicketRequest = StudentDiscountTicketRequest(

    document: StudentCard() ) // Ticketの発行をリクエスト let ticket = try? studentDiscountTicketRequest.issue() 利用側の実装イメージ
  9. © GO Inc. • 「1つのチケットリクエストが1つのチケットについての発行知識を知っている」という形を強制できる ので、オープン/クロースドの原則(※1)を守ることができる • 振る舞いと実挙動を分けることができるので、テストが書きやすい • 振る舞いに対してユニットテストを書くため、仕様変更が起きた際もユニットテストの変更量を少なく

    することができる • 各Protocolに振る舞いがある程度表現されているので、具体的な型を把握しなくてもプログラムを 書くことができるケースが有る ◦ some・anyキーワードとの相性が良い ポリモーフィズムのメリット 23 ※1: 既存のコードを変更せず、プログラムの機能を追加できること
  10. © GO Inc. class Ticket { } // チケットのSuperクラス final

    class NormalTicket: Ticket {} // 通常チケット final class StudentDiscountTicket: Ticket { } // 学割チケット // 発行するチケットタイプ enum IssueTicketType { case normal, studentDiscount } 28 ポリモーフィズムを利用せずに表現する場合 - 1
  11. © GO Inc. struct TicketRepository { let issueTicketType: IssueTicketType func

    isValid() -> Bool { switch issueTicketType { case .normal: // 通常チケット時の有効判定処理 case .studentDiscount: // 学割チケット時の有効判定処理 } } 29 ポリモーフィズムを利用せずに表現する場合 - 2
  12. © GO Inc. 30 func issue() -> Ticket? { guard

    isValid() else { return nil } switch issueTicketType { case .normal: // 通常チケットの発行処理 case .studentDiscount: // 学割チケットの発行処理 } } } ポリモーフィズムを利用せずに表現する場合 - 3
  13. © GO Inc. • 発行するチケットの種別を追加するたびに TicketRepositoryをメンテナンスし続けていく必要があ る。具体的には... ◦ 検証処理は各caseで異なるので、追加するたびに isValid()関数が長くなる

    ◦ 発行処理も各caseで異なるので、追加するたびに issue()関数が長くなる ◦ 検証が必要なチケットの場合、必要なドキュメントをプロパティとして持つ必要がある ▪ 実装によるが普通のチケットを発行したいだけなのに、学割チケットの検証処理に必要なインスタ ンスもinit時に渡さないといけなくなったりする ▪ 特定のチケットの処理を変更した時、全体をテストし直す必要がある → コード全体の見通しが悪くなり、意図せぬバグの発生や、メンテコストの増大、テスト工数の増大な ど、比較的大きな負の影響が発生する 31 ポリモーフィズムを用いなくても実装はできる。しかし...