Swiftでは、従来のOOP言語の経験が一般化され、プロトコルを単に継承するのではなく、組み合わせる(合成する)機能を通じて洗練されています。プロトコルの合成は、変数、関数の引数、またはジェネリックを複数のプロトコルに準拠するように宣言することを可能にします。このメカニズムは、複数の契約(インターフェース)に基づく動作を持つオブジェクトと柔軟に作業する必要がある際に非常に便利であり、多重継承の欠点を回避します。合成によって解決される問題は、「オブジェクトは要求のグループを満たすべきである」ということです。
Swiftの解決策では、特別な構文が使用されます:例えば、protocolA & protocolBというように、プロトコルを&(アンパサンド)で結合します。内部では、ランタイムチェック(例えば、型変換やジェネリックコンテキストでの変換)が行われます。これにより、タイプの数が最小化され、柔軟に「責任の分離」パターンが実装されます。
コードの例:
protocol Drawable { func draw() } protocol Movable { func move() } struct Sprite: Drawable, Movable { func draw() { print("Sprite draws") } func move() { print("Sprite moves") } } func animate(object: Drawable & Movable) { object.draw() object.move() } let s = Sprite() animate(object: s)
主な特長:
特定の構造体やクラスに結び付けることなく、protocolA & protocolB型の変数を作成できますか?
はい、変数を複数のプロトコルに同時に準拠するように宣言できます、例えば:
var obj: protocolA & protocolB
ただし重要なことは:このような変数はオブジェクト(値型ではなく)のみを参照でき、合成の中に少なくとも1つのプロトコルがクラス型制限されている(protocol: AnyObject)場合です。
クラス型を合成に含めることはできますか、例えば、SomeClass & Drawable?
はい、しかし注意が必要です:SomeClass & Protocolの形の合成は、値がそのクラス(またはその子クラス)のインスタンスであり、プロトコルを実装している必要があります。このアプローチは、ジェネリック型の制限に使用されます。
プロトコル拡張の関連型としてプロトコルの合成を使用できますか?
はい、しかし制限があります:関連型を合成として宣言することはできませんが、拡張でwhereを使用して合成プロトコルの制限を行うことは可能です、例えば、複数のプロトコルに準拠する型にのみ適用される拡張など。
プロジェクトでは、5つの類似したプロトコル—Drawable、Movable、Resizable、Colorable、Animatable—が実装されました。すべての場所でDrawable & Movable & Resizable & Colorable & Animatableの合成が使用されていました。典型的なエラーは、いくつかのエンティティが契約の1つを実装していないため、複雑なバグを引き起こしました。
利点:
欠点:
複雑な合成の代わりに、2つの主要なプロトコル(例えば、ActorとViewable)を明確にし、合成「DynamicEntity」のためにtypealiasを作成し、どこでもそれを使用しました。責任の領域を明確に区分しました。
利点:
欠点: