ソフトウェア設計

プロトコル指向プログラミング(POP)でOOPの限界を超える

プロトコル指向プログラミング(POP)は、何を継承したかではなく、何ができるかで型を設計します。継承の壁をプロトコルの組み合わせで解決する方法や、避けるべき場面までSwiftの例でまとめます。

読了 4 分
プロトコル指向プログラミング(POP)でOOPの限界を超えるのカバー画像

クラス継承で機能を拡張していると、親クラスがどんどん肥大化する経験は誰にでもあるでしょう。共通機能を追加し続けると、やがて触るのも怖い巨大な親クラスになります。

プロトコル指向プログラミング(POP)は、まさにこの点でOOPの限界を超えます。POPは「何を継承したか」ではなく「何ができるか」で型を設計します。継承の垂直構造ではなく、必要な能力をプロトコル単位で組み合わせます。

この記事では、OOPのどんな限界からPOPが生まれたのか、Swiftコードで何が違うのか、いつ使い、いつ避けるべきかを見ていきます。


OOPの継承はどこで行き詰まる?

オブジェクト指向の継承は強力です。しかし、三つの点で壁にぶつかりがちです。

一つ目は単一継承の限界です。Swiftのクラスは親を一つしか持てません。「ネットワークもキャッシュもできる」型を継承だけで作るのは困難です。

二つ目は肥大化した基底クラスです。共通機能を親に集めると、子は使わないメソッドまで受け継ぎます。

三つ目は値型との相性です。Swiftのstructとenumは継承できません。一方、Swift標準ライブラリの大半は値型で構成されています。

つまり継承中心で設計すると、Swiftが推奨する値型の利点を活かしにくくなります。


プロトコル指向プログラミング(POP)とは?

POPは2015年、AppleがWWDCで「Swiftはプロトコル指向の言語だ」と宣言したことで広く知られるようになりました。

要点は、プロトコルにデフォルト実装を追加できることです。プロトコルエクステンションを使えば、インターフェースだけでなく実際の動作も分けて提供できます。

たとえば次のように書きます。

protocol Greetable {
    var name: String { get }
}

extension Greetable {
    func greet() -> String {
        return "こんにちは, \(name)です"
    }
}

struct Person: Greetable {
    let name: String
}

print(Person(name: "ジフン").greet())
// 出力:こんにちは、ジフンです

Greetableに準拠するだけでgreet()が無料で使えます。継承せず、structにも機能を追加できるのです。

複数のプロトコルにも同時に準拠できます。ネットワークとキャッシュの能力を別々のプロトコルにすれば、必要な型に必要な能力だけを追加できます。

Person型にGreetable・Cacheableプロトコルを採用したクラス図
一つのプロトコルに一つの能力。必要なものだけを選んで追加する構造です

OOP継承とPOP、何が違う?

違いを表にまとめると次のとおりです。

項目 OOP継承 POP
コード再利用の方法 親クラスから継承 プロトコルエクステンションで組み合わせ
型の関係 垂直(is-a) 水平(can-do)
複数準拠 単一継承のみ 複数プロトコルに同時準拠
値型のサポート struct/enumは不可 struct/enumともに可能
結合度 親子が強く結び付く 能力単位で疎に分離

一言で言えばこうです。

継承は「あなたは何であるか」を表し、プロトコルは「あなたは何ができるか」を表します。

OOPとPOPの比較表が表示されたXcode画面とコーヒーの置かれた机
私はこんなとき「まず継承」ではなく「プロトコルで分けられないか」と考えます

いつPOPを使い、いつ避けるべきか?

POPは万能ではありません。状況に応じて選びましょう。

状況 判断
値型(struct/enum)に共通機能を分けたい POPが適切
異なる能力を組み合わせたい POPが有利
明確な階層(動物-哺乳類-犬)がある 継承でも十分
参照共有が重要なオブジェクト(例:ビューコントローラ) クラス継承が自然
プロトコルを細かく分けすぎて追跡しにくい 過剰な抽象化。見直す

基準は簡単です。値型や能力の組み合わせが必要ならPOP、明確な階層や参照共有が必要なら継承です。両者は対立せず、状況に応じて併用する道具です。

面接ではこう聞かれます

Q. POPはOOPのどんな限界を解決しますか?

単一継承と肥大化した基底クラスの問題を解決します。複数のプロトコルを組み合わせて必要な能力だけを追加でき、継承できないstruct/enumなどの値型にも、プロトコルエクステンションでデフォルト実装を提供できるためです。

Q. プロトコルエクステンションとクラス継承の違いは?

継承は垂直的なis-a関係で親を一つしか持てませんが、プロトコルは水平的なcan-do関係で複数に同時準拠できます。また継承は参照型のクラスだけに使えますが、プロトコルは値型にも適用できます。


継承が悪く、プロトコルが良いという二分法ではありません。ただSwiftで開発するなら、「まず継承」ではなく「これはプロトコルで分けられないか」と考えてみてください。その習慣が、より柔軟なコードにつながります。

あわせて読みたい