Slide 1

Slide 1 text

© GO Inc. Protocolを利用したポリモーフィズ ム(Polymorphism)とそのメリット 2023.04.12 開発本部ソフトウェア開発部ユーザーシステムグループ / 髙橋秀宗 GO株式会社

Slide 2

Slide 2 text

© GO Inc. 2 自己紹介 GO株式会社 ユーザーシステムグループ / 髙橋秀宗 タクシーアプリ『GO』のiOSアプリ開発を担当 趣味はギターとカメラ。 @h1d3mun3

Slide 3

Slide 3 text

Index © GO Inc. 1. ポリモーフィズムとは 2. モデリングから見るポリモーフィズム a. ユースーケースの定義 b. 実装 3. 終わりに 4. 参考資料 3

Slide 4

Slide 4 text

© GO Inc. ポリモーフィズムと は 01

Slide 5

Slide 5 text

© GO Inc. 複数の「型」に対してアクセスできる共通の接点です。 SwiftではProtocolを利用して表現できます。 5 ポリモーフィズムとは

Slide 6

Slide 6 text

© GO Inc. 6 🤔 ポリモーフィズムとは

Slide 7

Slide 7 text

© GO Inc. 7 動物における「食べる」動作 人 → 肉を、口で食べる 牛 → 草を、丸呑みして胃ですりつぶして食べる → 「食べる」という動作は動物全般に存在するが、その詳細は各動物 (≒型)によって異なる 例えば...

Slide 8

Slide 8 text

© 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関数を宣言 } 例えば...

Slide 9

Slide 9 text

© GO Inc. ポリモーフィズムが何故嬉しいのか、イメージしにくいですよね。 9 でも...

Slide 10

Slide 10 text

© GO Inc. 10 そこで! 学割の映画鑑賞券チケット発行フローをどのようにソースコード上に表現していくか、実際のコードととも に説明することを通じて、ポリモーフィズムの具体的なメリットについて説明します。

Slide 11

Slide 11 text

© GO Inc. モデリングから見 るポリモーフィズム 02

Slide 12

Slide 12 text

© GO Inc. ユースケースの定 義 2.a

Slide 13

Slide 13 text

© GO Inc. 13 映画館での学割チケットは、下記フローで発行されるものとします。 1. 申込書を書いて窓口に並ぶ 2. 窓口で学生証を提示する 3. 条件に問題がなければ発行 学割チケットの発行フロー

Slide 14

Slide 14 text

© GO Inc. 実装 2.b

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

© GO Inc. 16 // 申請に必要なドキュメントの表現 protocol ApplicationDocument { // 自身が有効かを判定するisValidという関数を持つ func isValid() -> Bool } struct StudentCard: ApplicationDocument { func isValid() -> Bool { } // 学生証が有効かを判定する } 学生証の表現

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

© GO Inc. 18 // 検証が必要なTicketを発行するTicketRequestを表現 protocol ValidationRequiredTicketRequest: TicketRequest { // 申請に必要なドキュメントをDocumentという型で持つ associatedtype Document: ApplicationDocument var document: Document { get } } 申請書の表現 - 2

Slide 19

Slide 19 text

© 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

Slide 20

Slide 20 text

© GO Inc. 20 各Protocol・各Structの関係性

Slide 21

Slide 21 text

© GO Inc. 21 // 学割チケットリクエストの生成 let studentDiscountTicketRequest = StudentDiscountTicketRequest( document: StudentCard() ) // Ticketの発行をリクエスト let ticket = try? studentDiscountTicketRequest.issue() 利用側の実装イメージ

Slide 22

Slide 22 text

© GO Inc. おわりに 03

Slide 23

Slide 23 text

© GO Inc. ● 「1つのチケットリクエストが1つのチケットについての発行知識を知っている」という形を強制できる ので、オープン/クロースドの原則(※1)を守ることができる ● 振る舞いと実挙動を分けることができるので、テストが書きやすい ● 振る舞いに対してユニットテストを書くため、仕様変更が起きた際もユニットテストの変更量を少なく することができる ● 各Protocolに振る舞いがある程度表現されているので、具体的な型を把握しなくてもプログラムを 書くことができるケースが有る ○ some・anyキーワードとの相性が良い ポリモーフィズムのメリット 23 ※1: 既存のコードを変更せず、プログラムの機能を追加できること

Slide 24

Slide 24 text

© GO Inc. 参考資料 04

Slide 25

Slide 25 text

© GO Inc. 25 ● ポリモーフィズム(Wikipedia) 参考資料

Slide 26

Slide 26 text

文章・画像等の内容の無断転載及び複製等の行為はご遠慮ください。 © GO Inc.

Slide 27

Slide 27 text

© GO Inc. おまけ XX

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

© GO Inc. 30 func issue() -> Ticket? { guard isValid() else { return nil } switch issueTicketType { case .normal: // 通常チケットの発行処理 case .studentDiscount: // 学割チケットの発行処理 } } } ポリモーフィズムを利用せずに表現する場合 - 3

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

文章・画像等の内容の無断転載及び複製等の行為はご遠慮ください。 © GO Inc.