web-dev-qa-db-ja.com

単一の構成オブジェクトは悪い考えですか?

ほとんどのアプリケーションで、ディスクからさまざまな設定を読み取ることを担当するシングルトンまたは静的な「config」オブジェクトを持っています。ほとんどすべてのクラスで、さまざまな目的で使用されています。基本的には、名前と値のペアのハッシュテーブルです。これは読み取り専用なので、私はグローバルな状態をたくさん持っているという事実にあまり関心がありませんでした。しかし、私は単体テストを始めているので、問題になり始めています。

1つの問題は、通常、同じ構成でテストすることを望まないことです。これにはいくつかの解決策があります:

  • 構成オブジェクトに、テストでのみ使用されるセッターを指定して、さまざまな設定を渡すことができるようにします。
  • 引き続き単一の構成オブジェクトを使用しますが、それをシングルトンから、必要なすべての場所に渡すインスタンスに変更します。その後、アプリケーションで1回、テストで1回、さまざまな設定で作成できます。

しかしどちらにしても、2つ目の問題が残ります。ほとんどすべてのクラスがconfigオブジェクトを使用できます。したがって、テストでは、テストするクラスの構成だけでなく、その依存関係もすべてセットアップする必要があります。これにより、テストコードが見苦しくなります。

この種の構成オブジェクトは悪い考えであるという結論に達し始めています。どう思いますか?いくつかの選択肢は何ですか?そして、どこでも構成を使用するアプリケーションのリファクタリングをどのように開始しますか?

45
JW01

単一の構成オブジェクトに問題はなく、すべての設定を1か所に保持することの利点を確認できます。

ただし、その単一のオブジェクトをどこでも使用すると、構成オブジェクトとそれを使用するクラスの間で高レベルの結合が発生します。構成クラスを変更する必要がある場合は、それが使用されているすべてのインスタンスにアクセスし、何も壊れていないことを確認する必要がある場合があります。

これに対処する1つの方法は、アプリのさまざまな部分で必要な構成オブジェクトの部分を公開する複数のインターフェースを作成することです。他のクラスが構成オブジェクトにアクセスできるようにするのではなく、インターフェースのインスタンスをそれを必要とするクラスに渡します。このように、configを使用するアプリの部分は、configクラス全体ではなく、インターフェース内のフィールドの小さなコレクションに依存します。最後に、単体テストでは、インターフェースを実装するカスタムクラスを作成できます。

これらのアイデアをさらに検討したい場合は、 [〜#〜] solid [〜#〜]原則 、特に インターフェース分離原理依存関係逆転原理

39
Kramii

関連する設定のグループをインターフェースで分離します。

何かのようなもの:

public interface INotificationEmailSettings {
   public string To { get; set; }
}

public interface IMediaFileSettings {
    public string BasePath { get; set; }
}

等.

現在、実際の環境では、単一のクラスがこれらのインターフェースの多くを実装します。そのクラスは、DBやアプリの設定などから取得する場合がありますが、多くの場合、ほとんどの設定を取得する方法を知っています。

しかし、インターフェースで分離すると、実際の依存関係がより明示的で細かくなり、これがテスト容易性の明らかな改善になります。

しかし..依存関係として設定を注入または提供することを常に強制される必要はありません。一貫性や明確さのために、私がすべきだと思われる議論があります。しかし、実際にはそれは不必要な制限のようです。

そこで、静的クラスをファサードとして使用し、どこからでも設定に簡単にアクセスできるようにします。その静的クラス内で、インターフェイスの実装を検索して設定を取得します。

私はサービスの場所がほとんどのことからすぐに気に入らないことを知っていますが、それに直面しましょう。コンストラクタを通じて依存関係を提供することは重要であり、時にはその重要性が私が負担する以上のものです。 Service Locationは、インターフェイスへのプログラミングを通じてテスト容易性を維持し、静的なシングルトンエントリポイントの利便性(慎重に測定され、適切に少数の状況)を提供しながら、複数の実装を可能にするソリューションです。

public static class AllSettings {
    public INotificationEmailSettings NotificationEmailSettings {
        get {
            return ServiceLocator.Get<INotificationEmailSettings>();
        }
    }
}

私はこのミックスがすべての世界で最高だと思います。

8
quentin-starin

はい、ご存知のとおり、グローバル構成オブジェクトは単体テストを困難にします。ユニットテストに「シークレット」セッターを設定することは簡単なハックです。ニースではありませんが、非常に便利です。これにより、ユニットテストの記述を開始できるので、コードをより良い設計に向けてリファクタリングできます。

(必須の参照: レガシーコードを効果的に使用する 。これには、レガシーコードを単体テストするためのこれとより多くの非常に貴重なトリックが含まれています。)

私の経験では、最善の方法は、グローバル構成への依存をできるだけ少なくすることです。しかし、これは必ずしもゼロではありません-それはすべて状況に依存します。グローバル構成にアクセスし、実際の構成プロパティを作成および使用するオブジェクトに渡すいくつかの高レベルの「オーガナイザー」クラスがあれば、オーガナイザーがそれだけを行う限り、うまく機能します。テスト可能なコード自体はあまり含まれていません。これにより、アプリの単体テスト可能な重要な機能のほとんどまたはすべてをテストしながら、既存の物理構成スキーマを完全に破壊することはできません。

これは、Spring for Javaのように、すでに本物の依存性注入ソリューションに近づいています。このようなフレームワークに移行できる場合は、問題ありません。しかし、実際には、特にレガシーアプリを扱う場合、DIに向けてゆっくりと細心の注意を払ってリファクタリングすることが、達成可能な最良の妥協策であることがよくあります。

4
Péter Török

私の経験では、Java=世界では、これがSpringの目的です。実行時に設定される特定のプロパティに基づいてオブジェクト(Bean)を作成および管理し、それ以外の場合は透過的です。アプリケーションです。Anonが言及しているtest.configファイルを使用するか、Springが処理する他のキー(ホスト名や環境など)に基づいてプロパティを設定するロジックを構成ファイルに含めることができます。

2番目の問題については、再設計によって回避できますが、見た目ほど深刻ではありません。これがあなたの場合に意味することは、たとえば、他のさまざまなクラスが使用するグローバルConfigオブジェクトがないことです。 Springを介して他のクラスを構成するだけで、構成オブジェクトはありません。構成を必要とするすべてのものがSpringを介してそれを取得したため、これらのクラスをコードで直接使用できます。

2
Justin Beal

この質問は、実際には構成よりも一般的な問題についてです。 「シングルトン」という言葉を読むとすぐに、そのパターンに関連するすべての問題をすぐに思いつきました。とりわけ、テストのしやすさは不十分です。

シングルトンパターンは「有害と見なされます」。つまり、それはalwaysの誤りではありませんが、-通常はの誤りです。 anythingのシングルトンパターンの使用を検討している場合は、検討を中止してください。

  • それをサブクラス化する必要がありますか?
  • インターフェースにプログラムする必要はありますか?
  • それに対して単体テストを実行する必要がありますか?
  • 頻繁に変更する必要がありますか?
  • アプリケーションは、複数の本番プラットフォーム/環境をサポートすることを目的としていますか?
  • メモリ使用量について少しでも心配ですか?

これらの答えが「はい」の場合(そしておそらく私が考えていなかった他のいくつかのことも)、シングルトンを使用したくないでしょう。多くの場合、構成には、シングルトン(つまり、インスタンスレスクラス)が提供できるよりもはるかに高い柔軟性が必要です。

苦労せずにシングルトンのほとんどすべての利点を必要とする場合は、SpringやCastleなどの依存性注入フレームワーク、または環境で利用可能なあらゆるものを使用します。そうすれば、宣言する必要があるのは一度だけであり、コンテナーはそれを必要とするものにインスタンスを自動的に提供します。

2
Aaronaught

C#でこれを処理した1つの方法は、インスタンスの作成中にロックし、すべてのクライアントオブジェクトで使用するためにすべてのデータを一度に初期化するシングルトンオブジェクトを用意することです。このシングルトンがキーと値のペアを処理できる場合、さまざまなクライアントおよびクライアントタイプで使用する複雑なキーを含む、あらゆる方法のデータを保存できます。動的に保ち、必要に応じて新しいクライアントデータを読み込む場合は、クライアントのメインキーの存在を確認し、見つからない場合は、そのクライアントのデータを読み込んで、メインディクショナリに追加します。また、プライマリ設定シングルトンにクライアントデータのセットを含めることができ、同じクライアントタイプの複数が同じデータを使用し、すべてプライマリ設定シングルトンを介してアクセスされます。この構成オブジェクトの構成の多くは、構成クライアントがこの情報にアクセスする方法と、その情報が静的か動的かによって異なります。必要なものに応じて、キー/値の設定と特定のオブジェクトAPIの両方を使用しました。

私の構成シングルトンの1つは、データベースからリロードするメッセージを受信できます。この場合、2番目のオブジェクトコレクションにロードし、コレクションを交換するためにメインコレクションのみをロックします。これにより、他のスレッドでの読み取りのブロックが防止されます。

構成ファイルからロードする場合は、ファイル階層を持つことができます。 1つのファイルの設定で、他のファイルのどれをロードするかを指定できます。このメカニズムは、それぞれ独自の構成設定を持つ複数のオプションコンポーネントを持つC#Windowsサービスで使用しました。ファイル構造パターンはすべてのファイルで同じでしたが、ロードされたか、プライマリファイル設定に基づいていませんでした。

0
Tim Butterfield

あなたが説明しているクラスは、関数ではなくデータを除いて、 神のオブジェクトのアンチパターン のように聞こえます。それが問題です。何らかの理由でデータが必要になった場合にのみ、データを再度読み取りながら、適切なオブジェクトに構成データを読み取って保存しておく必要があります。

さらに、適切ではない理由でシングルトンを使用しています。シングルトンは、複数のオブジェクトが存在すると不良な状態になる場合にのみ使用してください。この場合にシングルトンを使用することは不適切です。構成リーダーが複数ある場合でも、すぐにエラー状態が発生することはないはずです。複数ある場合、おそらくそれは間違っていますが、構成リーダーを1つだけ持つ必要は必ずしもありません。

最後に、そのようなグローバルな状態を作成することは、データに直接アクセスする必要があるよりも多くのクラスを許可するため、カプセル化の違反になります。

0
indyK1ng

多くの設定があり、関連するものの「まとまり」がある場合、それらをそれぞれの実装で別々のインターフェースに分割することは理にかなっていると考えます-次に、DIで必要な場所にそれらのインターフェースを挿入します。テスト容易性、低カップリング、SRPを獲得できます。

Los TechiesのJoshua Flanaganからこの問題に触発されました。 彼は記事を書いた 少し前の件で。

0
Grant Palin