2023年04月12日にオンラインで開催されたYUMEMI.grow Mobile #2で発表した資料です。 プログラムにおけるポリモーフィズムの考え方とそれを実現するSwiftのProtocolという機能について、なるべく皆様の実際の業務でご活用いただけるようなイメージが湧くよう、実際のコードや、いくつかの事例などを多く含めながらわかりやすさを優先して発表させていただきました。
© GO Inc.Protocolを利用したポリモーフィズム(Polymorphism)とそのメリット2023.04.12開発本部ソフトウェア開発部ユーザーシステムグループ / 髙橋秀宗GO株式会社
View Slide
© GO Inc. 2自己紹介GO株式会社ユーザーシステムグループ / 髙橋秀宗タクシーアプリ『GO』のiOSアプリ開発を担当趣味はギターとカメラ。@h1d3mun3
Index© GO Inc.1. ポリモーフィズムとは2. モデリングから見るポリモーフィズムa. ユースーケースの定義b. 実装3. 終わりに4. 参考資料3
© GO Inc.ポリモーフィズムとは01
© GO Inc.複数の「型」に対してアクセスできる共通の接点です。SwiftではProtocolを利用して表現できます。5ポリモーフィズムとは
© GO Inc. 6🤔ポリモーフィズムとは
© GO Inc. 7動物における「食べる」動作人 → 肉を、口で食べる牛 → 草を、丸呑みして胃ですりつぶして食べる→ 「食べる」という動作は動物全般に存在するが、その詳細は各動物(≒型)によって異なる例えば...
© GO Inc. 8protocol 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関数を宣言}例えば...
© GO Inc.ポリモーフィズムが何故嬉しいのか、イメージしにくいですよね。9でも...
© GO Inc. 10そこで!学割の映画鑑賞券チケット発行フローをどのようにソースコード上に表現していくか、実際のコードとともに説明することを通じて、ポリモーフィズムの具体的なメリットについて説明します。
© GO Inc.モデリングから見るポリモーフィズム02
© GO Inc.ユースケースの定義2.a
© GO Inc. 13映画館での学割チケットは、下記フローで発行されるものとします。1. 申込書を書いて窓口に並ぶ2. 窓口で学生証を提示する3. 条件に問題がなければ発行学割チケットの発行フロー
© GO Inc.実装2.b
© GO Inc. 15protocol Ticket {static func issue() -> Self // 自分自身を発行するissueという接点を持つ}protocol ValidationRequiredTicket: Ticket { } // 検証が必要なTicketを表現// 学割チケットを検証が必要なTicketとして表現struct StudentDiscountTicket: ValidationRequiredTicket {static func issue() -> StudentDiscountTicket {return StudentDiscountTicket()}}チケットの表現
© GO Inc. 16// 申請に必要なドキュメントの表現protocol ApplicationDocument {// 自身が有効かを判定するisValidという関数を持つfunc isValid() -> Bool}struct StudentCard: ApplicationDocument {func isValid() -> Bool { } // 学生証が有効かを判定する}学生証の表現
© GO Inc.protocol TicketRequest {// 発行したいTicketをIssueTicketTypeという型で持つassociatedtype IssueTicketType: Ticket// チケットの発行を行うissueという関数を持つfunc issue() throws -> IssueTicketType}// TicketRequestでTicketを発行できなかったときのエラーenum TicketRequestIssueError: Error {case validationFailure}17申請書の表現 - 1
© GO Inc. 18// 検証が必要なTicketを発行するTicketRequestを表現protocol ValidationRequiredTicketRequest: TicketRequest {// 申請に必要なドキュメントをDocumentという型で持つassociatedtype Document: ApplicationDocumentvar document: Document { get }}申請書の表現 - 2
© GO Inc. 19// 学割チケットのTicketRequeststruct StudentDiscountTicketRequest: ValidationRequiredTicketRequest {// IssueTicketTypeがStudentDiscountTicket型と宣言typealias IssueTicketType = StudentDiscountTicket// DocumentはStudentCard型と宣言typealias Document = StudentCardlet document: Documentfunc issue() throws -> StudentDiscountTicket {guard document.isValid() else { throw TicketRequestIssueError.validationFailure }return IssueTicketType.issue()}}申請書の表現 - 3
© GO Inc. 20各Protocol・各Structの関係性
© GO Inc. 21// 学割チケットリクエストの生成let studentDiscountTicketRequest = StudentDiscountTicketRequest(document: StudentCard())// Ticketの発行をリクエストlet ticket = try? studentDiscountTicketRequest.issue()利用側の実装イメージ
© GO Inc.おわりに03
© GO Inc.● 「1つのチケットリクエストが1つのチケットについての発行知識を知っている」という形を強制できるので、オープン/クロースドの原則(※1)を守ることができる● 振る舞いと実挙動を分けることができるので、テストが書きやすい● 振る舞いに対してユニットテストを書くため、仕様変更が起きた際もユニットテストの変更量を少なくすることができる● 各Protocolに振る舞いがある程度表現されているので、具体的な型を把握しなくてもプログラムを書くことができるケースが有る○ some・anyキーワードとの相性が良いポリモーフィズムのメリット23※1: 既存のコードを変更せず、プログラムの機能を追加できること
© GO Inc.参考資料04
© GO Inc. 25● ポリモーフィズム(Wikipedia)参考資料
文章・画像等の内容の無断転載及び複製等の行為はご遠慮ください。© GO Inc.
© GO Inc.おまけXX
© GO Inc.class Ticket { } // チケットのSuperクラスfinal class NormalTicket: Ticket {} // 通常チケットfinal class StudentDiscountTicket: Ticket { } // 学割チケット// 発行するチケットタイプenum IssueTicketType {case normal, studentDiscount}28ポリモーフィズムを利用せずに表現する場合 - 1
© GO Inc.struct TicketRepository {let issueTicketType: IssueTicketTypefunc isValid() -> Bool {switch issueTicketType {case .normal:// 通常チケット時の有効判定処理case .studentDiscount:// 学割チケット時の有効判定処理}}29ポリモーフィズムを利用せずに表現する場合 - 2
© GO Inc. 30func issue() -> Ticket? {guard isValid() else { return nil }switch issueTicketType {case .normal:// 通常チケットの発行処理case .studentDiscount:// 学割チケットの発行処理}}}ポリモーフィズムを利用せずに表現する場合 - 3
© GO Inc.● 発行するチケットの種別を追加するたびにTicketRepositoryをメンテナンスし続けていく必要がある。具体的には...○ 検証処理は各caseで異なるので、追加するたびに isValid()関数が長くなる○ 発行処理も各caseで異なるので、追加するたびに issue()関数が長くなる○ 検証が必要なチケットの場合、必要なドキュメントをプロパティとして持つ必要がある■ 実装によるが普通のチケットを発行したいだけなのに、学割チケットの検証処理に必要なインスタンスもinit時に渡さないといけなくなったりする■ 特定のチケットの処理を変更した時、全体をテストし直す必要がある→ コード全体の見通しが悪くなり、意図せぬバグの発生や、メンテコストの増大、テスト工数の増大など、比較的大きな負の影響が発生する31ポリモーフィズムを用いなくても実装はできる。しかし...