web-dev-qa-db-ja.com

クラスにプログラム全体を表すのは悪い設計ですか?

シングルトンを使用する代わりに、すべてのコンポーネントのインスタンスを保持する1つのクラスを作成しました。 MainAppとしましょう。 MainAppはプログラムのエントリポイントで初期化され、すべてのコンポーネントとリソースの寿命を決定します。それは単一の責任があり、それはコンポーネントのコレクションです。それが最初に悪い考えのように感じられなかった理由です。ただし、一部のコンポーネントは他のコンポーネントにアクセスする必要があります。他のほとんどすべてのコンポーネントに必要なコンポーネントが1つあります。これを解決するために依存性注入を使用しましたが、これによりこれらのコンポーネントがMainAppに依存します。 MainAppがすべてのコンポーネントの大きなメディエータークラスになったようです。

私の質問に戻る:この種のクラスは悪いデザインですか?より良い代替手段は何でしょうか?または、このクラスは必ずしも悪いデザインではありませんか?問題は、多くがそれに依存しているという事実に関連していますか?

18
TheNappap

このMainAppクラス自体は悪い考えではありませんが、おそらく車輪を再発明します。

一般に、依存性注入のアイデア全体を調整するには、1つの「中央」コンポーネントが必要です。また、既製のソリューション(Java a Weldコンテナなど)を使用することをお勧めします。

ただし、サービスはnotMainAppに依存する必要がありますが、必要なその他のサービスに依存します。たとえば、リポジトリサービスを必要とするユーザーサービスがあるとします。あなたの質問はあなたが今これをしているように聞こえます:

// BAD
class UserService {
    private MainApp mainApp;

    UserService(MainApp mainApp) {
        this.mainApp = mainApp;
    }

    void doSomethingThatNeedsARepository() {
        this.mainApp.getRepositoryService().doStuff();
    }
}

// in MainApp
UserService userService = new UserService(this);

これを行うと、どのサービスが他のどのサービスを必要とするかを制御できなくなり、大きな混乱が生じます。

実際に必要なものだけを注入する:

// Better
class UserService {
    private RepositoryService repository;

    UserService(RepositoryService repository) {
        this.repository = repository;
    }

    void doSomethingThatNeedsARepository() {
        this.repository.doStuff();
    }
}

// in MainApp
RepositoryService repository = new RepositoryService(...);
UserService userService = new UserService(repository);

(具体的なサービスの代わりにインターフェースを使用することのメリットについて話し合うかもしれませんが、おそらく他の誰かがこれを行うでしょう;-))

31
mtj

ただし、一部のコンポーネントは他のコンポーネントにアクセスする必要があります。他のほとんどすべてのコンポーネントに必要なコンポーネントが1つあります。これを解決するために、依存性注入を使用しましたが、これにより、これらのコンポーネントがMainAppに依存します。 MainAppがすべてのコンポーネントの大きなメディエータークラスになったようです。

MainAppを依存性注入のコンポジションルートとして使用しても、他のコンポーネントにMainAppが存在することを通知するような愚かなことをしている場合を除き、他の依存関係はありません。

MainAppの仕事は、コンポーネントを構築して注入することです。コンポーネントが話したり質問したりする必要があるものではありません。それに固執すればあなたのデザインは素晴らしいです。

これを行っても、プログラム全体をMainAppが「代表」するわけではありません。永続オブジェクトグラフを構築する方法です。それが完了すると、MainAppは、一部のコンポーネントでstart()を呼び出して脇に移動することができます。

10
candied_orange

MainAppで結構です。作曲ルートです。それは接着剤として機能し、依存性を注入し、プロデューサーとリスナーをリンクするようなものです。

依存関係の注入については、MainApp自体を注入しないでください(プラットフォームの制限により強制されない限り)。代わりに、MainAppにファクトリーを含む他のものを注入させることができます。注入するためだけに新しいタイプを作成する必要がある場合は、それを実行してください。

他にはMainAppの知識があってはなりません。 MainAppの変更は、コンポーネントの変更を意味するものではありません。

5
Theraot

TL; DR;MainAppクラスを注入すると、注入されたすべてに疎結合されます。 MainAppクラスは、あらゆる種類の問題を解決する場所になります。なぜなら、あなたが言うように、これは、注入されるすべてのもののメディエーターになるからです。各クラスに必要なものを注入するだけです。

長い説明:

Inversion of Controlコンテナーまたは略してIoCを作成したようです。これは実際には、SOLIDアーキテクチャによって決定される依存関係の逆転の原則を支えています。アイデアは、中央リポジトリがアプリケーションのさまざまな部分を構築する方法を理解し、インスタンス化されたインスタンスへの参照を保持するか、少なくとも、最小限の構成で新しいインスタンスを構築する機能があります。

リフレクションを利用する多くの言語は、クラスが必要とするさまざまな依存関係を静的に分析し、それらがどのように構築されるかに関する知識に基づいて再帰的に依存関係を構築することで、これをさらに一歩進めることができます。これは、アプリケーションを構築する際にIoCにクラスを登録し、いくつかの非常に複雑な依存関係ツリーの自動構築を即座に許可するだけなので、非常に強力です。

現時点では、この種のデザインから遠く離れているようには見えませんが、MainAppクラスを他のクラスに挿入しないでください。これは、本当に分離したままにすることを確実にしたいときに、レジストリに依存関係を作成します。代わりに、レジストリを渡してクラスに必要なものを引き出しさせるのではなく、実際に必要なクラスのインスタンスを使用してクラスを構築する必要があります。

言語に応じて、以下からアイデアを引き出すために利用できる優れたIoCインプリメンテーションがすでにいくつかあります。

等々。

2
David Barker