はじめに
Protocol Oriented Programmingというものがあるよと先輩に教えていただいたので、Jon HoffmanのProtocol-Oriented Programming with Swiftを読んでみました。
本を読む前に軽く調べてみたのですが、イメージが全く沸かず、一冊の本としてまとまっているものを読むことで理解してみようとしたのがきっかけです。
そもそもなんなの?
僕の理解ですと、Swiftの挑戦的な取り組みの一部なのかなと思っています。むしろそう考えたほうが「OOPとはちょっと雰囲気が違うのか」と一歩引いて考えられるようになる気がしています。
Protocol Oriented Programming(以下POP)を一言で言えば、Protocol Extensionという仕組みを利用してコーディングを進めていくスタイルです。このProtocol Extensionは、Object Oriented Programming、オブジェクト指向プログラミング(以下OOP)でいう、Interfaceのデフォルト実装です。
OOPではInterfaceはあくまでも実装を強制させるときや、クラスの依存先を抽象的にするために使うものでしかありません。
ここに、実際の実装が入っているのがProtocol Extensionです。
イメージを説明
OOPでクラスに共通機能を持たせる時には継承という仕組みを用いるという選択肢があります。継承を行うことでスーパークラスが持つ振る舞いをサブクラスにも持たせることができます。とても便利な機構ですが、基本的に多重継承は前提としていないためにこの関係を成長させていくと階層構造が出来上がります。
階層構造には階層の持たせ方に一意性がないという問題があります。生き物のクラスを設計するという状況を例にして説明します。
生物クラスの下に動物と植物で分けて動物の下に赤い動物とサブクラスを切っていくパターンと、生物クラスの下に生物の色(赤と青)で分けるパターンなどを考えることができます。どちらも最下層のサブクラスは同じですが階層が異なります。この階層が異なることで、「動物」という振る舞いを実際に持たせる方法が変わってしまいます。
生物の下に動物と植物でサブクラスを切った場合は動物の振る舞いを動物クラスに持たせることができます。しかし先に赤い生物と青い生物でサブクラスを切った場合は動物の振る舞いをそれぞれ赤い動物クラスと青い動物クラスに持たせないといけません。
このようにクラス階層の組み方によって特定の振る舞いごとの保守性が変わってしまいます。その振る舞いのパターンの切り分けが階層の上位にくればくるほど保守しやすくなりますが、何が上位にくるべきかはソフトウェアの変更を考えると予想はつきません。
そこで、この問題を解決するためにPOPのメリットを活かすことができます。それぞれの振る舞いをプロトコルとして宣言し拡張することで階層構造から脱することができます。
クラスはプロトコルをいくつも実装することができるので、このように必要なプロトコルを選んで実装するだけでよくなります。インターフェースと違うのは、それぞれのプロトコルが機能を持っているということです。ただクラスに機能の実装を要求するだけでなく、実装されたクラスがその機能を使えるようにすることができます。これがProtocol Extensionです。
実際のところどうなのか
SwiftはOOPで書くこともできるので、無理にPOPで書く必要は全くありません。
そういった状況で、POPは実際にどう位置づけられているのかを経験から整理してみました。
いいところ
クラス設計を心配する必要がなくなる
クラスの継承は、1つのクラスからしか行うことができません。この制約のせいで、必ずクラス階層が生まれます。階層が生まれるということは、階層をメンテナンスする必要があるということです。今後どのように拡張されていくのかわからないアプリケーションなのにも関わらず、今後のことを心配して設計しなければいけないということは時間の無駄です。今後どうなるかなんてわかりもしないからです。むしろわからないという前提の方がよいからです。
さくっとクラスの処理を共通化したいというときには、プロトコルはとても役に立ちます。
デザインパターンがシンプルに書ける
GoFのデザインパターンではよく抽象クラスを用いた実装があります。POPを使うことで抽象クラスの代替としてプロトコルを用いることができます。OOPでは抽象クラスの下に具体クラスを作る必要があるケースでも、プロトコルとその拡張で済むためよりシンプルな作りにすることができます。
拡張がやりやすい
クラス階層がある場合は、基底クラスを拡張する時に機能としては関係のないクラスまで影響を及ぼすため、影響範囲が大きくなりがちです。POPの場合は、影響範囲はプロトコルに限定されるため、影響しているかどうかをチェックするときはそのプロトコルが実装されたクラスだけを確認すれば問題なくなります。
悪いところ
プロトコルの管理が煩わしくなる。
階層構造を使わなくなるため、どのプロトコルがなんの意味を持つのかをきちんと管理する必要が出てきます。クラス継承であれば、クラスが継承された先を見るだけでどのように利用しているかがはっきりとわかるためあまり管理する必要がありませんが、プロトコルは多くのクラスに実装される前提で作られているため意図がはっきりとわかりづらくなってしまいます。
そのため、きちんとプロトコルの目的を整理して管理する必要があります。
どのプロトコルが使われてるのかがわかりづらい
僕がプロトコルのここは嫌いだなーと初めて思ったのが、これです。特にSwiftUIはPOPをふんだんに使っているイメージがあり、よく躓いた覚えがあります。例えばなにかの構造体を作って別の構造体に渡すとき、不思議な挙動をすることがあります。
その不思議な挙動はだいたいProtocol Extensionで実装されている事が多いです。この構造体が実装しているあのProtocolがこれをやっているのかみたいに、OOPでいう基底クラスの存在の意識みたいなものができなくなるため理解に時間がかかります。
プロトコルが違う用途で実装されてしまう。
Protocol Extensionで実装されていたロジックを任意のクラスで使えてしまうという問題が出てきます。プロトコルの目的をきちんと理解されていない場合、Utilクラスと扱いはほぼ同じになるので、注意が必要です。これを避けるためには、Protocol Extensionをするときにきちんと制約を設計する必要があります。
この制約の設計がとても癖が強く、ここにPOPで開発した経験が最も活きていくのだろうなと感じています。
まとめ
POPはまだ発展途上なのできちんと工夫を凝らして設計しないとデメリットが強くなるので、生じる問題を理解して取り組む必要があると思いました。クラス階層から逃れることはできますが、制約をしっかりと決めなければならないという問題が新たに発生します。その問題への取り組みについて今後は考えていきたいと思っています。