ProgrammingSwiftミドル開発者

Swiftにおけるプロトコルの合成とは何か、それはどのように機能し、何のために必要なのか?同時に複数のプロトコルを使用する際の落とし穴は何か?

Hintsage AIアシスタントで面接を突破

回答。

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を使用して合成プロトコルの制限を行うことは可能です、例えば、複数のプロトコルに準拠する型にのみ適用される拡張など。

一般的な間違いやアンチパターン

  • 8〜9のプロトコルを使用した合成:これはアーキテクチャの過負荷と責任の分配が不適切であることの兆候です
  • value型(構造体)をAnyObject制限のプロトコル合成変数にキャストする:常にエラーを引き起こします
  • アプリケーションの異なる部分で同じ合成を使用し、typealiasを使用しない:可読性を損ないます

実生活の例

ネガティブケース

プロジェクトでは、5つの類似したプロトコル—Drawable、Movable、Resizable、Colorable、Animatable—が実装されました。すべての場所でDrawable & Movable & Resizable & Colorable & Animatableの合成が使用されていました。典型的なエラーは、いくつかのエンティティが契約の1つを実装していないため、複雑なバグを引き起こしました。

利点:

  • 深い継承は不要
  • 機能を簡単に追加または削除できます

欠点:

  • 不一致を追跡するのが難しい
  • 複雑なテスト
  • 宣言の可読性が低い

ポジティブケース

複雑な合成の代わりに、2つの主要なプロトコル(例えば、ActorとViewable)を明確にし、合成「DynamicEntity」のためにtypealiasを作成し、どこでもそれを使用しました。責任の領域を明確に区分しました。

利点:

  • コードの読みやすさと保守性が向上
  • テストはDynamicEntityの動作を明確に特定
  • 要件リストの変更が迅速

欠点:

  • アーキテクチャの再考が必要
  • 既存のクラスを要件に合わせて分割する必要がある場合もあります。