問題の歴史:
プロトコルの抽象化は、Swiftでオブジェクト指向プログラミングのクラス的継承に対抗して登場しました。Objective-Cや他のOOP言語では、「一般から特殊への」継承のアプローチが支配的でしたが、Swiftは初期からプロトコルを主な抽象化手段として推進し、継承の上に構成を強調しました。
問題:
従来の継承は厳格な階層を想定しており、サブクラスのツリーがあり、overrideによって必ず拡張される必要があります。これは柔軟性を制限し、「壊れやすい」コードを生み出し、リファクタリングを難しくし、基底クラスの肥大化を引き起こします。さらに、Swiftではクラスの多重継承がサポートされていないため、機能の再利用は他のメカニズムを通じてのみ可能です。
解決策:
プロトコルの抽象化により、型が実装しなければならない「要求のセット」を宣言することができます。プロトコルは共通のロジックを注入するために拡張 (extension) することができ、これにより「ミックスイン」の概念に近づきます。
コードの例:
protocol Drawable { func draw() } extension Drawable { func draw() { print("デフォルトの描画") } } struct Circle: Drawable {} let c = Circle() c.draw() // "デフォルトの描画"が出力されます
主な特徴:
プロトコルのextensionとクラスの通常の実装の違いは何ですか?
プロトコルを通じたextensionは、ユーザーが自分の型でこのメソッドを実装していない場合にのみ、デフォルトの実装を追加します。メソッドが型で明示的に実装されている場合、それが呼び出されます。
例:
protocol Demo { func foo() } extension Demo { func foo() { print("デフォルト") } } struct X: Demo { func foo() { print("カスタム") } } X().foo() // "カスタム"
プロトコルを実装している型とそのextensionにデータとしてアクセスした場合に何が起こりますか?
プロトコルがメソッドを必須(要求)として宣言する場合、プロトコル型にキャストしても、特定の型の実装が使用されます。ただし、extensionに新たに(プロトコルで以前に宣言されていない)プロパティが追加された場合、それはextensionを通じてのみアクセス可能で、プロトコル型ではアクセスできません。
異なるstructを持つインスタンスをプロトコル実装の配列に保存できますか?
はい — 「存在的」型(例: [Drawable])のおかげで、異種コレクションを保存できます:
struct Tri: Drawable { func draw() { print("三角形") } } let arr: [Drawable] = [Circle(), Tri()] arr.forEach { $0.draw() }
会社には基本的なスーパークラス Shape があり、すべての図形(Circle、Square、Polygon)が継承されていました。基本クラスは肥大化し、各新しい図形をoverrideでサポートしなければならなくなりました。システムの拡張はますます難しくなり、各新しい型がABIを壊し、既存のコードを再記述させていました。
利点:
欠点:
複数のプロトコル Drawable、Colorable、Animatable を使用することにしました。これにより、各図形を他の構造を変更せずに「アニメーション可能かつ色付き」に簡単にすることができました。新しい機能はextensionを通じて追加されます。
利点:
欠点: