プロトコル拡張は、Swiftでプロトコル指向プログラミングの理念をサポートするために導入されました。プログラマは、基本クラスやグローバル関数を介さずに、プロトコルレベルでメソッドのデフォルト実装を導入できます。これにより、コードの重複を減らし、型の動作を柔軟に適応させることができます。
問題は、デフォルト実装が独自の実装(オーバーライド)を隠してしまう場合や、責任の線引きが曖昧になる場合に発生します。特に、型が交差する複数のプロトコルを実装している場合に問題が生じます。さらに、型自体でメソッドが実装されている場合、常にそのメソッドが拡張からの実装を上書きします。
解決策は、プロトコル拡張を一般的な動作のためにのみ使用し、特定のケースでは明示的に型内でメソッドを実装することです。同じプロトコルの二つの拡張で同じメソッドのオーバーロードを避けることが望ましいです。
コードの例:
protocol Flyer { func fly() } extension Flyer { func fly() { print("デフォルトの飛行") } } struct Bird: Flyer {} let sparrow = Bird() sparrow.fly() // 出力: デフォルトの飛行
主な特徴:
プロトコル拡張はプロトコルにストレージプロパティを追加できますか?
いいえ、計算プロパティとメソッドのみがプロトコル拡張に追加できます。ストレージプロパティは許可されていません。
プロトコルと型に同名の異なるメソッドの実装がある場合、何が起こりますか?どれが呼び出されますか?
型に直接アクセスする場合は型の実装が呼ばれ、プロトコル型の参照を通じてインスタンスにアクセスすると、プロトコル拡張の実装が呼ばれます。
protocol Greeter { func greet() } extension Greeter { func greet() { print("拡張からこんにちは") } } struct Person: Greeter { func greet() { print("型からこんにちは") } } let person = Person() person.greet() // 型からこんにちは let greeter: Greeter = person greeter.greet() // 拡張からこんにちは
プロトコル拡張で制約のためにwhere制約を使用できますか?
はい。これは強力な機能の一つで、特定の型のみにプロトコルを拡張することができます。
extension Collection where Element: Equatable { func allEqual() -> Bool { guard let first = self.first else { return true } return allSatisfy { $0 == first } } }
** ネガティブケース
チームはプロトコル拡張を介してエラーロギングを実装することを決定しましたが、各サービスが特定の形式を追加したいかもしれないということを考慮していませんでした。その結果、異なるサービスがプロトコルへの参照を通じて関数を呼び出し、動作のロジックが期待とは異なります。
利点:
欠点:
** ポジティブケース
プロトコルは常に普遍的な振る舞いがある場合にのみ拡張されます。特別なケースでは、型内でメソッドを明示的に実装し、競合する場所に対してコードレビューを行います。
利点:
欠点: