web-dev-qa-db-ja.com

依存関係の逆転の原則(Swift)-多態性なしでも適用可能? (抽象化:制約付きジェネリック)

Swiftを使用した Dependency Inversion Principle (DIP)を説明する記事/ブログは多数あります。いくつか例を挙げると(上位のGoogleヒット):

さて、Swiftは(Appleでさえ)プロトコル指向のプログラミング言語として広く説明されており、OOP言語ではなく、当然これらの記事は抽象化を実現しています継承ではなくプロトコルを使用するDIPで、ただし、実装の詳細を知らなくても、ポリシーレイヤーがポリモーフィズムを使用して下位レイヤーを呼び出すことができるようにする、特に異種のプロトコル。

// Example A
protocol Service {
    func work()
}

final class Policy {
    // Use heterogeneous protocol polymorphically.
    private let service: Service

    init(service: Service) { self.service = service }

    func doWork() { service.work() }
}

final class SpecificService: Service {
    func work() { /* ... */ print("Specific work ...") }
}

let policy = Policy(service: SpecificService())

// Resolves to doWork() of SpecificService at runtime
policy.doWork() // Specific work ...

ポリモーフィズムはDIPの重要な概念の1つであることを理解しています(私が間違っていない限り)が、Swift実装の観点から見ると、ランタイムではなくプロトコルに制約されたジェネリックを使用してDIPが適用されているのがわかりますポリモーフィズム。例:

// Example B
protocol Service {
    func work()
}

final class Policy<PolicyService: Service> {
    private let service: PolicyService

    init(service: PolicyService) { self.service = service }

    func doWork() { service.work() }
}

final class SpecificService: Service {
    func work() { /* ... */ print("Specific work ...") }
}

let policy = Policy(service: SpecificService())

// policy.service specialized as SpecificService instance at compile time
policy.doWork() // Specific work ...

DIPとSwiftのコンテキストでジェネリックを使用する人を見たことがありません。そのため、私はおそらくここで暗闇の中にいるので、この質問です。

設計の原則として、私は[〜#〜] b [〜#〜]が達成すると信じています[〜#〜] a [〜#〜]と同じ目標、wrt浸漬;静的に型付けされたプロトコル指向のプログラミング言語で、継承よりも一般的に構成を優先する場合、具体的には(afaik)プロトコルやジェネリックをプロトコルやポリモーフィズムよりも優先する場合は、[〜#〜] b [〜#〜]。これは当然、一度に1つの特殊なPolicyのみを使用し、デカップリング/依存関係の逆転による低レベルの詳細の変更を容易にするためにDIPにフォールバックするという制約の下にあります。


質問:[〜#〜] b [〜#〜]上記は、コンパイル時に具体的なPolicyを専門とするServiceが「知っている」場合でも、DIPの有効なアプリケーションと見なされます(ジェネリックのため、制約として適用される抽象化によって分離されます)一般的なプレースホルダー)?

5
dfri

免責事項:私はスウィフトを知りません。 Swift GenericsがC#のGenericsのようなものであり、それらがかなりのように見える場合、具体的なサービスについてポリシーが "知っている"とは言えません。具体的なサービスに固有のメソッドを実際に呼び出すことはできないため、Serviceプロトコルを通じて公開されているものを操作する必要があります。具体的な型で機能するバージョンを生成する方法をコンパイラが知っているという事実は、実際には影響しませんコードの結合。Serviceプロトコルに準拠している限り、ポリシーに影響を与えることなく、具体的なサービスの実装の詳細を変更できます。これは、DIPに準拠しているため、関係するメカニズムがわずかに異なるだけです。

とは言っても、ここで多くのことは得られないと思います(Swiftに私が知らないことに固有のものがない限り)。クライアントコードの観点から、2つのバリアントはほとんど同じです。コードメンテナンスの観点からは、少し混乱しているように感じます(IMO)。また、私が知る限り、コンパイラはおそらく一般的な制約に基づいてvtableを生成するため、必ずしもパフォーマンスが向上するとは限りません。動的ディスパッチを使用して、正しい型の正しいメソッドを多態的に呼び出します(ジェネリックはC++テンプレートと同じように機能しません)確かに、コンパイラーは特定の状況でその一部を最適化できる場合がありますが、それでも、それはあなたの部分の時期尚早な最適化に相当することになるでしょう(さらに、実際のパフォーマンスの向上があるかどうかを判断するために測定を行う必要がありますが、実際にはそうではないと仮定します)。

追伸DIPは、継承ベースのポリモーフィズムのように、DIPを使用するために一般的に使用される言語メカニズムを必ずしも含む必要はありません。それは、高レベルのコンポーネントが低レベルのコンポーネントに直接依存すべきではなく、どちらも抽象化に依存すべきであると述べています。この抽象化は、上位レベルのコンポーネントが「所有」している(定義されている、または規定されている)必要があります。概念的には、上位レベルのコンポーネントの一部を形成します。下位レベルのコンポーネントは、この抽象化によって課される「契約」に準拠する必要があります。逆転は、下位レベルのコンポーネントが他の2つのコンポーネントに依存するようにすることで実現します。したがって、抽象化の役割はプロトコルまたは基本クラスによって実現されることが多いですが、高レベルのコンポーネントがカスタムコマンドを既知の場所にあるファイルに書き込むなど、かなり奇妙なことを行うこともできます。低レベルのコンポーネントによって読み取られ、解釈されます。ここでの抽象化は、通信メカニズム、ファイルの場所、およびカスタムコマンドセットの組み合わせであり、これもすべて、高レベルコンポーネントによって定義および所有されていると見なされ、低レベルコンポーネントはこれらの要件に準拠している必要があります。 。それは異常ですが、それでもDIPを確認します。それは正確な実装ではなく、さまざまな要素がシステムの異なる部分間の結合を制御するためにどのように相互作用するかについてです。

4