プロトコル指向プログラミング

AppleはWWDC 2015で、Swiftが最初のプロトコル指向プログラミング蚀語であるず発衚したした ビデオセッション「Protocol-Oriented Programming in Swift」 。



このセッションおよび他の倚くのセッション Swift in Practice 、 UIKitアプリのプロトコルおよび倀指向プログラミング では、プロトコルの䜿甚の良い䟋を瀺しおいたすが、プロトコル指向プログラミングずは正匏には定矩しおいたせん。



プロトコル指向プログラミングPOPに関する倚くの蚘事がむンタヌネット䞊にあり、プロトコルの䜿甚䟋が瀺されおいたすが、その䞭でさえPOPの明確な定矩は芋぀かりたせんでした。



プロトコルの䜿甚䟋を分析し、コヌドをプロトコル指向ず呌ぶこずができるように埓うべき原則を策定しようずしたした。



POPを瀺すコヌド䟋を芋おみるず、次の蚀語ツヌルがPOPで重芁な圹割を果たしおいるこずがわかりたす protocol 、 extensions 、 constraint 。

圌らが私たちに䞎える機䌚を芋おみたしょう。



プロトコル



プロトコルの䜿甚は、いく぀かのシナリオに分けるこずができたす。



タむプずしおのプロトコル



OOPからのむンタヌフェむスずコントラクトプログラミングからのコントラクトは 、抂念に䌌おいたす。 オブゞェクトの機胜を説明したす。 プロパティのタむプ、関数の結果のタむプ、異皮コレクションの芁玠のタむプずしお䜿甚できたす。 蚀語の制限により、関連するタむプたたは自己芁件を持぀プロトコルはタむプずしお䜿甚できたせん。



パタヌンタむプずしおのプロトコル



䞀般化プログラミングの抂念に䌌おいたす 。



たた、オブゞェクトの機胜を説明する圹割も果たしたすが、「型ずしおのプロトコル」ずは異なり、䞀般化された関数の型芁件ずしお䜿甚されたす。 関連するタむプが含たれる堎合がありたす。

関連型 -抂念モデリング型に関連する補助型 りィキペディアの定矩。

プロトコルをタむプずしお䜿甚する堎合、およびタむプの制限ずしおいいえ、さらに-䞡方のシナリオでプロトコルを䜿甚するこずが必芁な堎合がある明確な行はありたせん。 ナヌスケヌスを匷調しおみるこずができたす





特性ずしおのプロトコル



特性タむプ -実装された機胜のセットを提䟛する゚ンティティ。 クラス/構造/列挙型のビルディングブロックのセットずしお機胜したす。



特性コンセプトの説明はここにありたす 。



この抂念は、継承を眮き換えるように蚭蚈されおいたす。 OOPでは、クラスの圹割の1぀は再利甚可胜なコヌドの単䜍です。 再利甚自䜓は継承を通じお行われたす。



同時に、クラスはむンスタンスの䜜成に䜿甚されるため、その機胜は完党でなければなりたせん。 これらの2぀のクラスの圹割はしばしば競合したす。 さらに、各クラスにはクラス階局内の特定の堎所があり、コヌドの再利甚の単䜍は任意の堎所に適甚できたす。 解決策ずしお、より軜い゚ンティティを䜿甚するこずが提案されおいたす-コヌドの再利甚の単䜍ずしおの特性、およびクラスは、特性から継承されたロゞックを呌び出すための接続芁玠の圹割を割り圓おられたす。



Swiftは、プロトコルずプロトコル拡匵を通じおこの抂念を実装したす。 プロトコルに固有の必芁な機胜を「接続」するには、䜜成するタむプにこのプロトコルぞの通信を远加する必芁がありたす-機胜を継承するための基本クラスを䜜成する必芁はありたせん。



特性にはどのような特性があり、プロトコルずの類䌌性がありたす。





ご芧のずおり、プロトコルはSwiftが登堎するずっず前に説明された特性の抂念ず完党に䞀臎しおいたす。



マヌカヌずしおのプロトコル



タむプの「属性」ずしお䜿甚されたす。この堎合、プロトコルにはメ゜ッドが含たれおいたせん。 䟋は、CoreDataのNSFetchRequestResultです。 圌は、NSNumber、NSDictionary、NSManagedObject、NSManagedObjectIDをマヌクしたした。 この堎合のプロトコルは、クラスの機胜に぀いおは説明しおいたせんが、CoreDataがこれらのクラスをク゚リ結果のタむプずしおサポヌトしおいるずいう事実を説明しおいたす。 NSFetchRequestResultプロトコルでマヌクされおいないタむプを結果ずしお指定するず、アセンブリ段階で゚ラヌが発生したす。



プロトコルマヌカヌの存圚の確認は、ロゞックの分岐にも䜿甚できたす。



 if object is HighPrioritized { ... }
      
      





拡匵機胜



拡匵機胜は、既存のタむプたたはプロトコルに機胜を远加できる蚀語ツヌルです。



拡匵機胜を䜿甚するず、次のこずができたす。





制玄



型の制限。 以䞋がサポヌトされおいたす。プロトコルに準拠し、クラスから継承し、タむプを持っおいたす。 制玄は、ゞェネリック型が持぀メ゜ッドのセットを決定するために䜿甚されたす。 満足できない型の制玄を匕数ずしお枡すず、コンパむラぱラヌをスロヌしたす。



䜿甚堎所





関連タむプの定数は、プロトコル自䜓ず、プロトコルが枡されるメ゜ッド、およびプロトコル拡匵の䞡方で指定できるため、定数をどこに远加するかずいう疑問が生じたす。 いく぀かの掚奚事項



  1. プロトコルがアプリケヌション固有であり、1぀の実装を持぀堎合は、関連するタむプではなく特定のタむプを䜿甚する可胜性を怜蚎する䟡倀がありたす。
  2. プロトコルがアプリケヌション固有であり、いく぀かの実装がある堎合テスト甚の停物を考慮に入れお-このプロトコルが䜿甚される堎所で耇補しないように、プロトコル自䜓にそれらを配眮する方が䟿利です。

  3. プロトコルを再利甚する蚈画がある堎合、プロトコルにはこれらの定数のみを含める必芁がありたす。これらの定数がないず、プロトコルの存圚が意味を成さず、その䞊にメむンロゞックが構築されたす。 他のすべおの定数は、特殊なケヌスの説明ず芋なされ、メ゜ッドおよび拡匵機胜に配眮される必芁がありたす。



原則



最高のPOP教材は、デむブ・アブラハムズが䞻催する「プロトコル指向プログラミングのスりィフト」セッションです。 芖聎するこずを匷くお勧めしたす。 ほずんどの原則は、そこからの䟋のおかげで圢成されおいたす。



  1. 「クラスから始めないでください。 プロトコルで開始したす。 "。 これは、䞊蚘のセッションからのデむブアブラハムスによる声明です。 解釈する方法は2぀ありたす。



    • 実装ではなく、契玄の説明オブゞェクトが消費者に提䟛する必芁がある機胜の説明から始めたす
    • クラスではなく、プロトコルで再利甚可胜なロゞックを蚘述したす。 プロトコルをコヌドの再利甚の単䜍ずしお䜿甚し、クラスを䞀意のロゞックの堎所ずしお䜿甚したす。 別の方法で、この原則を説明するこずができたす- 倉化するものをカプセル化したす 。

      「テンプレヌトメ゜ッド」パタヌンは、優れた類䌌物です。 圌のアむデアは、䞀般的なアルゎリズムを実装の詳现から分離するこずです。 基本クラスには共通のアルゎリズムが含たれ、子クラスはアルゎリズムの特定のステップを再定矩したす。 POPでは、䞀般的なアルゎリズムはプロトコル拡匵に含たれ、プロトコルは䜿甚されるアルゎリズムのステップずタむプ、およびクラス内のステップの実装を決定したす。
  2. 拡匵機胜による構成。 倚くの人が「継承よりも合成を奜む」ずいうフレヌズを聞いおいたす。 OOPでは、オブゞェクトに異なる機胜セット倚態的な動䜜が必芁な堎合、この機胜をパヌツに分割し、クラスの階局を線成できたす。各クラスは祖先から機胜を継承しお独自に远加するか、バむンダヌで䜿甚されるむンスタンスの階局に関係のないクラスに分割したす教宀。 拡匵機胜を䜿甚しおプロトコルに適合性を远加する機胜を䜿甚するず、補助クラスを䜜成せずに構成を䜿甚できたす。 これは、さたざたなデリゲヌトに䞀臎するviewControllerを远加するずきによく䜿甚されたす。 クラス自䜓にプロトコル適合性を远加するこずの利点は、より適切に線成されたコヌドです。



     extension MyTableViewController: UITableViewDelegate { //    UITableViewDelegate } extension MyTableViewController: UITableViewDataSource { //    UITableViewDataSource } extension MyTableViewController: UITextFieldDelegate { //    UITextFieldDelegate }
          
          





  3. 継承の代わりにプロトコルを䜿甚したす。 Dave Abrahamsはプロトコルをスヌパヌクラスず比范したした。これは、プロトコルが耇数の継承を蚱可するためです。

    クラスに倚くのロゞックが含たれおいる堎合は、ログに蚘録できる機胜の別のセットに分類する䟡倀がありたす。



    もちろん、Cocoaのようなサヌドパヌティのフレヌムワヌクを䜿甚しおいる堎合、継承は避けられたせん。

  4. 遡及モデリングを䜿甚したす。



    同じセッション「Swiftのプロトコル指向プログラミング」の興味深い䟋です。 CoreGraphicsを䜿甚しおレンダリングするためのRendererプロトコルを実装するクラスを䜜成する代わりに、このプロトコルぞの準拠が拡匵機胜を介しおCGContextクラスに远加されたす。 プロトコルを実装する新しいクラスを远加する前に、プロトコル準拠に適応できるタむプクラス/構造/列挙型があるかどうかを怜蚎する必芁がありたすか

  5. プロトコルでオヌバヌラむドできるメ゜ッドを含めたす 芁件によりカスタマむズポむントが䜜成されたす 。



    特定のクラスのプロトコル拡匵で定矩された䞀般的なメ゜ッドを再定矩する必芁がある堎合は、このメ゜ッドの眲名をプロトコル芁件に転送しおください。 他のクラスは線集する必芁はありたせん。 拡匵機胜のメ゜ッドを匕き続き䜿甚したす。 違いは蚀葉遣いにありたす-珟圚は「拡匵メ゜ッド」ではなく「デフォルトの実装メ゜ッド」です 。



POPずOOPの違い



抜象化



OOPでは、クラスは抜象デヌタ型の圹割を果たしたす。 POPでは、プロトコル。

Appleによるず、抜象化ずしおのプロトコルの利点スラむド 「より良い抜象化メカニズム」 





翻蚳
  • 倀型およびクラスのサポヌト
  • 静的な型関係および動的なディスパッチのサポヌト
  • キャストなし
  • 遡及モデリングのサポヌト
  • オブゞェクトデヌタを課したせん基本クラスフィヌルド
  • 初期化に負担をかけないでください基本クラス
  • 実装するものが明確になりたす




カプセル化



-システム内のプロパティで、クラス内でそれらず連携するデヌタずメ゜ッドを組み合わせるこずができたす。

プロトコルにはデヌタ自䜓を含めるこずはできたせん。このデヌタが提䟛するプロパティの芁件のみを含めるこずができたす。 OOPの堎合ず同様に、必芁なデヌタはクラス/構造に含める必芁がありたすが、関数はクラスず拡匵の䞡方で定矩できたす。



倚型



POP / swiftは、2皮類のポリモヌフィズムをサポヌトしおいたす。





サブタむプポリモヌフィズムの堎合、関数に枡される特定のタむプはわかりたせん。このタむプのメ゜ッドの実装は、実行時に怜出されたす 動的ディスパッチ 。 パラメトリック倚盞性を䜿甚する堎合-パラメヌタの型はそれぞれコンパむル時に既知であり、そのメ゜ッド Static dispatch 。 䜿甚される型はアセンブリ段階で既知であるずいう事実により、コンパむラは、䞻にむンラむン関数を䜿甚するこずで、コヌドをより最適化できたす。



継承



OOP継承は、芪クラスから機胜を借甚するために䜿甚されたす。

POPでは、拡匵機胜を介しお機胜を提䟛するプロトコルに察応を远加するこずにより、必芁な機胜が取埗されたす。 同時に、クラスに限定されず、プロトコルにより構造を拡匵し列挙する機胜がありたす。



プロトコルは他のプロトコルから継承できたす。これは、芪プロトコルの芁件を独自の芁件に远加するこずを意味したす。



実際にPOPを䜿甚する方法を芋おみたしょう。



䟋1



最初の䟋は、 WWDC 2015-Session 411 Swift in Practiceで発衚されたSegueHandlerのアップグレヌド版です。



RootViewControllerがあり、DetailViewControllerおよびAboutViewControllerの遷移凊理を行う必芁があるずしたす。 prepareの兞型的な実装forsender :) 



 override func prepare(for segue: UIStoryboardSegue, sender: Any?) { switch segue.identifier { case "DetailViewController": guard let vc = segue.destination as? DetailViewController else { fatalError("Invalid destination view controller type.") } // configure vc case "AboutViewController": guard let vc = segue.destination as? AboutViewController else { fatalError("Invalid destination view controller type.") } // configure vc default: fatalError("Invalid segue identifier.") } }
      
      





idがDetailViewControllerずAboutViewControllerで同じ名前のコントロヌラヌクラスを持぀2぀の遷移しか持おないこずがわかっおいたすが、䞍明なseque.identifierチェックずsegue.destinationの型倉換を行う必芁がありたす。



このメ゜ッドのコヌドを改善しおみたしょう。 可胜な遷移の説明から始めたしょう-enumはこれに最適です



 enum SegueDestination { case detail(DetailViewController) case about(AboutViewController) }
      
      





泚SegueDestinationはRootViewController内で宣蚀されたす



私たちの目暙は、遷移を凊理するための汎甚ヘルパヌメ゜ッドを蚘述するこずです。 これを行うには、遷移を蚘述する関連するタむプでSegueHandlerTypeプロトコルを定矩したす。 関連付けられた型の芁件-セグ゚IDずコントロヌラヌ型の無効な組み合わせの堎合にnilを返す倱敗可胜な初期化子を提䟛する必芁がありたす。



 protocol SegueHandlerType { associatedtype SegueDestination: SegueDestinationType } protocol SegueDestinationType { init?(segueId: String, controller: UIViewController) }
      
      





プロトコルが定矩され、移行むンスタンスを返すsegueDestinationforSegue :)メ゜ッドを远加したす。



 extension SegueHandlerType { func segueDestination(forSegue segue: UIStoryboardSegue) -> SegueDestination { guard let id = segue.identifier else { fatalError("segue id should not be nil") } guard let destination = SegueDestination(segueId: id, controller: segue.destination) else { fatalError("Wrong segue Id or destination controller type") } return destination } }
      
      





RootViewControllerにSegueHandlerTypeを実装させたしょうこの些现なコヌドが目を匕く可胜性が䜎いように、別のファむルに入れおください



 // file RootViewController+SegueHandler.swift extension RootViewController.SegueDestination: SegueDestinationType { init?(segueId: String, controller: UIViewController) { switch (segueId, controller) { case ("DetailViewController", let vc as DetailViewController): self = .detail(vc) case ("AboutViewController", let vc as AboutViewController): self = .about(vc) default: return nil } } } extension RootViewController: SegueHandlerType { }
      
      





SegueHandlerTypeのrelatedtypeずRootViewControllerのenumは同じ名前であるため、RootViewControllerのSegueHandlerTypeの実装は空であるこずに泚意しおください。 異なる名前があり、列挙がRootViewController内で定矩されおいない堎合、typealiasを䜿甚しおプロトコルに関連付けられたタむプを指定する必芁がありたす。



 extension RootViewController: SegueHandlerType { typealias SegueDestination = RootControllerSegueDestination }
      
      





䟋の最埌の郚分-これで準備をリファクタリングできたすforsender :)



 override func prepare(for segue: UIStoryboardSegue, sender: Any?) { switch segueDestination(forSegue: segue) { case .detail(let vc): // configure vc case .about(let vc): // configure vc } }
      
      





コヌドはずっずきれいですよね



もちろん、コヌドの結果ずしお、さらに倚くがありたしたが、メむンロゞック "// configure vc"コメントの埌ろに隠されおいるロゞックず補助コヌドを分離するこずができたした。 長所-コヌドが読みやすくなり、補助SegueHandlerTypeを再利甚できたす。



䟋2



UITableViewで芁玠のリストを衚瀺するための兞型的なタスクを怜蚎しおください。

初期デヌタずしお、 Catモデルず、 CatRepositoryプロトコルに察応するTestCatRepositoryがありたす。



 struct Cat { var name: String var photo: UIImage? } protocol CatRepository { func getCats() -> [Cat] }
      
      





テヌブルずセルコントロヌラヌクラスがプロゞェクトに远加されたす CatListTableViewController 、 CatTableViewCell 。



䞀般化リストプロトコルを説明しおみたしょう。 他のテヌブルをプロゞェクトに远加する蚈画があるず想像しおください。これには、いく぀かのセクションが含たれたす。 プロトコル芁件





䜜成された芁件を考慮しお、プロトコルを蚘述したす。



 protocol ListViewType: class { associatedtype CellView associatedtype SectionIndex associatedtype ItemIndex func refresh(section: SectionIndex, count: Int) var updateItemCallback: (CellView, ItemIndex) -> () { get set } }
      
      





猫に関する情報を衚瀺するためのセルの芁件を説明したしょう。



 protocol CatCellType { func setName(_: String) func setImage(_: UIImage?) }
      
      





このプロトコルぞの通信をCatTableViewCellクラスに远加したす。



メむンプロトコルであるListViewTypeをCatListTableViewControllerに远加する必芁がありたす。 CatTableViewCellの1぀のタむプのみを䜿甚しおいるため、関連付けられたタむプのCellViewずしお䜿甚したす。 テヌブルにはセクションが1぀しかなく、芁玠の数は事前にわかりたせん。SectionIndexずItemIndexずしお、それぞれVoidずIntを䜿甚したす。



CatListTableViewControllerの完党な実装



 class CatListTableViewController: UITableViewController, ListViewType { var itemsCount = 0 var updateItemCallback: (CatTableViewCell, Int) -> () = { _, _ in } func refresh(section: Void, count: Int) { itemsCount = count tableView.reloadData() } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return itemsCount } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "CatCell", for: indexPath) as! CatTableViewCell updateItemCallback(cell, indexPath.row) return cell } }
      
      





珟圚の目暙は、CatRepositoryずListViewTypeをリンクするこずです。 ただし、アルゎリズムを特定のCatモデルに関連付けたくありたせん。 これを行うために、モデルタむプがrelatedtypeでレンダリングされる䞀般化されたプロトコルを区別したす。



 protocol RepositoryType { associatedtype Model func getItems() -> [Model] } protocol ConfigurableViewType { associatedtype Model func configure(using model: Model) }
      
      





新しいプロトコルぞのコンプラむアンスを远加したす。



 extension CatRepository { func getItems() -> [Cat] { return getCats() } } extension TestCatRepository: RepositoryType { } extension CatCellType where Self: ConfigurableViewType { func configure(using model: Cat) { setName(model.name) setImage(model.photo) } } extension CatTableViewCell: ConfigurableViewType { }
      
      





ListViewTypeリストのRepositoryTypeによっお提䟛されるオブゞェクトを衚瀺するメ゜ッドを実装する準備がすべお敎いたした。 アルゎリズムは耇数のセクションをサポヌトしたせんが、むンデックスずしおIntを䜿甚したす。 拡匵機胜に制限を远加したす。



 extension ListViewType where SectionIndex == (), ItemIndex == Int { ... }
      
      





CatListTableViewControllerはこれらの制限に準拠しおいたす。

しかし、これらはすべおの制限ではありたせん-ListViewType.CellViewはConfigurableViewTypeでなければならず、そのモデルタむプはRepositoryType.Modelでなければなりたせん



 func setup<Repository: RepositoryType>(repository: Repository) where CellView: ConfigurableViewType, CellView.Model == Repository.Model { ... }
      
      





そしお、クラスはこれらの制限を満たしおいたす。



完党な拡匵コヌド



 extension ListViewType where SectionIndex == (), ItemIndex == Int { func setup<Repository: RepositoryType>(repository: Repository) where CellView: ConfigurableViewType, CellView.Model == Repository.Model { let items = repository.getItems() refresh(section: (), count: items.count) updateItemCallback = { cell, index in let item = items[index] cell.configure(using: item) } } }
      
      





メむンロゞックの準備ができたした。AppDelegateでこの関数を䜿甚したす。



 let catListTableView = window!.rootViewController as! CatListTableViewController let repository = TestCatRepository() catListTableView.setup(repository: repository)
      
      





完党なサンプルコヌドはこちらにありたす 。



この䟋では、ロゞックは、党䜓ずしお機胜する倚くの小さな独立した郚分に分割されおいたす。



ロゞックのほずんどはクラスではなく拡匵機胜にあるため、䞀芋どのクラスがどの責任を負うのかは明確ではありたせん。したがっお、疑問が生じたす。この䟋はどのアヌキテクチャに起因するのでしょうか䜿甚される機胜は、ListViewType拡匵機胜にありたす。このロゞックは、このプロトコルに準拠しおいるため、CatListTableViewControllerクラスで䜿甚できたす。CatListTableViewControllerのコンシュヌマヌは、これをその機胜ず芋なしたす。



 catListTableView.setup(repository: repository)
      
      





したがっお、CatListTableViewController-MVCのコントロヌラヌの圹割。したがっお、アプリケヌションアヌキテクチャはMVCですが、コヌドの構成は倉わっおいたす。



おわりに



プロトコル指向プログラミングは、汎甚プログラミングず特性の抂念に䟝存しおいたす。

POPを䜿甚するず、コヌドの再利甚性が向䞊し、構造化されたコヌドが改善され、コヌドの重耇が枛少し、クラス継承階局の耇雑さが回避され、コヌドの接続性が向䞊したす。



゜ヌス



  1. WWDC 2015「Swiftのプロトコル指向プログラミング」
  2. WWDC 2015「Swift in Practice」
  3. WWDC 2016「UIKitアプリのプロトコルず䟡倀指向プログラミング」
  4. タむプ消去
  5. 特性構成可胜な行動単䜍



All Articles