web-dev-qa-db-ja.com

クリーンアーキテクチャの「境界を越える」

私は素人のようなプログラミングのような教育を受けていませんが、最初のAutoIt、次にC++でいくつかのゲームのボットをプログラミングするために、自由時間のかなりの部分を費やしました。無料の関数のみを使用した非常に手続き型のプログラミングを紹介されました。数週間前、私の焦点は、実際にこれらのボットを作成することから、プロのプログラマーがプログラムを構築する方法を理解することに移り、必然的にOOP、デザインパターン、SOLID、そして最近ではClean Architectureに出会いました。

これらの手法は、私が書く小さなプログラムにとって逆効果である可能性が高いことを十分に理解していることを、前もって述べておきたいと思います。しかし、学習の興味から彼らと知り合いたいです。

現在、プログラムを小さな部分に分割してSRPに固執しようとしています。クリーンアーキテクチャの背後にあるアイデアを実装しようとしています。それはCleanが対象としているものよりも信じられないほど小さいプログラムであり、私がそれに取り組む唯一の人であるため、コントローラー、プレゼンター、ビューモデル、ビューなどへの分離にあまり厳格ではありません–またGUIを完全に別の場所に配置するために、プログラムを別のDLLに分割する予定はありません。ただし、ロジックが(ビジネス)ロジックに完全に依存するという考えは好きですが、ロジックはGUIをまったく認識していません。そのため、GUIモジュールを別のプロジェクトにあるかのように扱います。ただし、「境界を越える」ことは、特に外部からDLLからアプリケーションへの場合である場合は、私が生きている間、実装方法を理解できません。

私が間違っている場合は修正してください。Interactor-Objectにリクエストを送信して、いくつかのエンティティを処理し、最終的にはGUIの変更を処理するPresenterに結果を返すコントローラーがあります。基本的に、私の入力は、アプリでの作業後に何らかの値を返す関数を呼び出します。この関数呼び出しは、コマンドパターンのスタイルでInteractor-Objectに配置されます。公開されている境界インターフェース*を実装しています。コントローラは呼び出すことができる署名を知る必要があるため、最初にコントローラを構築する必要があります。したがって、そのインターフェイスは、外部のDLL=がアプリケーションに要求を送信するためのアクセスポイントになります。

ただし、インタラクターは、構築時にビジネスエンティティとプレゼンターへの参照を取得して、それらと相互作用できるようにする必要はありませんか?コントローラーはエンティティについて何も知らないため、コントローラーをインターアクターに渡すことはできません。つまり、コントローラーは、インターアクターの既に構築されたコンクリーションを呼び出す必要があります。それはどのように行われますか?私の考えでは、アプリケーションは具体的なインタラクターをコントローラーに渡す必要があります。これは、「内部のサークル」は外部のサークルについて知っているはずがないため禁止されています。

これは、境界を外側に横断する2番目の部分に私を連れて行きます:Interactorはそれを知らずに外側の円に応答をどのように渡しますか?アプリケーションがプレゼンターが実装する必要のあるインターフェースをアプリケーションが提供するため、インタラクターがプレゼンターのシグネチャを知っているため、これは一種のオブザーバーパターンであると想像できます。ただし、プレゼンターは、上記と同じ質問に終わる具体的なInteractorオブジェクトに自分自身を登録する必要があります。

よろしくお願いします。ご意見をお待ちしております。

TL; DR:UseCases/Interactorsをどこでインスタンス化し、これらのコンクリーションに外部からどのようにアクセスしますか?

3

はい、インタラクターはコントローラーに注入されます。アプリケーションが起動すると、メインモジュールはすべてのアプリケーションオブジェクト(インタラクター、コントローラー、ビューなど)をインスタンス化します。原則として、コントローラーは独自のインタラクターをインスタンス化できますが、一般的にはこれをお勧めしません。インタラクターはコントローラーよりも中心に近いレイヤーにあり、メインモジュールは最も外側のレイヤーであるため、依存関係ルールに関してはどちらの方法でも問題ありません。

境界をまたぐ場合については、依存関係ルールに違反していないことを確認するために必要なことは何でも行うことができます。インタラクターの呼び出しは、単なるメソッド呼び出しである場合があります。単純な要求/応答通信がある場合、結果はそのメソッドの戻り値になる可能性があります。または、より複雑なものを処理している場合は、コールバック(イベント)が含まれる場合があります。アプリケーションにとって最も意味のあるものを決定する必要があります。

現在、本は手元にありませんが、「メイン」に関するセクションまたは章があるはずです。あなたはそこにもう少し詳細を見つけることができます。

2
doubleYou

最初に、Robert Martinの Clean Architecture というタイトルの本を読んでいないことを強くお勧めします。あなたはそれを読んだと思いますが、確かにわかりません。彼が言った本を出版する前に、あなたの質問とよく似た多くの質問がなされたからです。

彼の本を読んだ場合は、戻って第26章「主要コンポーネント」をもう一度見てください。

あなたがその本を所有していない場合(および所有していない他の人のために)、指定された章を要約します。

この章の最初の数段落では、次のように述べています。

どのシステムにも、他のコンポーネントを作成、調整、監視するコンポーネントが少なくとも1つあります。このコンポーネントをmainと呼びます。

Mainコンポーネントは、究極の詳細、つまり最下位レベルのポリシーです。これは、システムの初期エントリポイントです。オペレーティングシステム以外は何にも依存しません。その仕事は、すべての工場、戦略、およびその他のグローバルな施設を作成し、システムの高レベルの抽象的な部分に制御を渡すことです。

そして、その章の「結論」セクションには次のように書かれています:

Mainをアプリケーションへのプラグインと考えてください-初期条件と構成を設定し、すべての外部リソースを収集し、制御を高レベルに引き渡すプラグインアプリケーションのポリシー。

本当に速いものを指定させてください。 「コンポーネント」という言葉について言及します(すでに述べましたが)技術的には、コンポーネントはコードの意味ではありません。簡単に(多分単純すぎるかもしれませんが)、「コンポーネント」はボブおじさんが基本的に関連するコード/コードファイルのグループを参照するために使用する単語です(実際の定義が必要な場合は彼の本を参照してください)。

したがって、「コンポーネント」について話すときは、その「コンポーネント」内のコードについて実際に話していることを思い出してください。

では、あるコンポーネントが別のコンポーネントにどのように依存するのでしょうか。まあ、ボブおじさんによると、コンポーネント(コードのグループ)の依存関係は、別のコンポーネントでanythingを参照するたびに作成されます。それがインターフェースでも、型定義でも、単純な変数名でもかまいません。

技術的にはコンポーネントを別のコンポーネントに注入できないため、これを覚えておいてください。あるコンポーネントを別のコンポーネントに注入することについて話すとき、私はほとんど常に、それらのコンポーネントに含まれているインターフェース/クラスのレベルでの依存性注入について話している。

さて、引用に戻ります:これらは、プログラムのエントリポイントであるMainコンポーネント(たとえば、C++の「メイン」ファイル、またはNodeJSサーバーで実行する最初のファイルなど)が、具体的なクラスをインスタンス化し、それらを依存関係に与える(「注入」はその専門用語です)か、依存関係にファクトリー(これは知らない人のための設計パターンです)を与える必要があります。具体的なクラス自体。

Robert Martinの本の第14章「コンポーネントのカップリング」で、彼は第26章のトピックを非常にうまく説明していると私が思う画像を提供します。

enter image description here

注:これはComponentディペンデンシーグラフです。

「メイン」が依存しているコンポーネントの数に注意してください。また、「メイン」に依存しているコンポーネントがないことにも注意してください。これは、「メイン」コンポーネントが他のすべてのコンポーネントのすべての配線を行うため、互いの具象クラスの実装について知る必要がなく、代わりにインターフェース/抽象化のみに依存できるためです。

(うまくいけば)私が言おうとしていることの基本的な理解ができたところで、依存関係注入が発生する順序について説明しましょう。

注:ここから先は、インターフェイス/クラスのコンテキストでのみ話をしていることを思い出してください。

依存関係を作成して相互に注入する順序は、次の2つの要素に依存します(意図的ではありません)。

  1. 依存性注入メカニズム ;

コンストラクターを介して依存関係を注入する場合、依存関係は依存関係を作成beforebefore(---)し、作成中に依存関係に注入する必要があります。

ただし、依存関係のメソッドを介して依存関係を注入できる場合は、依存関係を依存関係の後に作成し、その後単純に注入することができます。

(メソッドなどを通じて)任意の時点で実行できる注入メカニズムを使用している場合、次のポイントは実際には適用されません。

  1. 依存関係の種類。

依存関係はusing依存関係ですか、それともimplementing/inheriting依存関係ですか?

たとえば、Component B内の具象クラスがComponent Aからのインターフェースを使用すると想像してください。

enter image description here

つまり、具象クラスは、インターフェイスが別のクラスによって他の場所に実装されていると想定しています。それはインターフェースを使用するだけで、インターフェースから継承しません。

ここで、Component Cに具象クラスがあり、usesが同じであるインターフェースコンポーネント自体。この具象クラスは、このインターフェースを実装せず、それだけを使用します。

enter image description here

インターフェイスの実装は実際にはComponent Dにあります。

enter image description here

もう一度言いましょう。Component C内では、具象クラスがインターフェースを使用しますが、そのインターフェースは実際には =)implementedin Component D

'uses'と 'implements'の関係は、どちらも等しく依存関係です。しかし、これは重要な部分です。依存関係が注入される順序に関しては、 'uses'関係のみが重要です。

そのステートメントを例で説明しましょう:

まず、次のような依存関係グラフがあるとします。 enter image description here

先に述べたように、このコンテキストで探している関係はuses関係だけなので、黒い矢印のみを考慮してください。

Component #4Component #3に依存します。 Component #4は、Component #2に依存するComponent #1に依存します。

コンストラクタを通じてすべての依存関係を注入しているとしましょう。

擬似コードでは、Mainコンポーネントは次のようになります。

// Instantiate the concrete implementation within Component #1:
implementation1 = new Implementation1() 

// Instantiate the concrete implementation within Component #2 while injecting its dependency into it:
implementation2 = new Implementation2(implementation1) 

// Instantiate the concrete implementation within Component #3:
implementation3 = new Implementation3() 

// Instantiate the concrete class within Component #4 while injecting both of its dependencies into it:
 implementation4 = new Implementation4(implementation2, implementation3)

// Okay, everything has been wired together, now hand control off to your highest level application logic.

Implementation3Implementation1Implementation2の前に簡単にインスタンス化できた可能性があることに注意することが重要ですが、数値順にインスタンス化することにしました。

2
Fearnbuster