問題: 構成のスプロール化 時間の経過とともに、プログラムは機能とオプションを獲得します。外部のシステムやサービス (データベース、イベント ブローカー、クラウド/Web サービスなど) に接続するときは、それらのサービスの構成と資格情報のセットを手元に置いておく必要もあります。
実行時にこの情報を保存する従来の場所は、グローバル変数と OS 環境変数です。どちらも吸う。構成データは、論理的には「グローバル環境」またはアプリが実行されているコンテキストですが、グローバル変数または環境変数に簡単に依存することはできません。
もう 1 つの従来のメカニズムである構成ファイル (XML、INI、.properties など) は、実行間で構成データを保存するのに役立ちますが、プログラム コードまたはその実行中に構成/オプションを整理することは何もしません。
オプションをアプリケーションのクラスのプロパティにすることで、少しきれいにすることができます。これが従来の「次のステップ」です。残念ながら、「何がどこに行くのか??」という事前の準備が必要になる場合があります。考え。IMO、それ以上の価値があります。それらの選択を正しく行ったとしても、それは長続きしません。十分な機能を備えたアプリを使用している場合、時間の経過とともにオプションと設定の数が膨大になります。オブジェクトコンストラクターのデフォルトとオプション、および他のメソッドの引数/コードを手作業でコーディングするのに多くの時間を費やすことになります。クラス間で構成を完全に分割しようとすると、労力がかかるだけでなく、クラスの相互依存性が高くなる可能性があります。きれいなカプセル化はこれで終わりです!
私はこの特定の壁にしばしば頭をぶつけてきました。特に、すべてに対して「合理的」または「インテリジェント」なデフォルトと動作を備え、ユーザーがいつでもデフォルトをオーバーライドできるようにし、簡単なインターフェースを提供するコードを目指している場合はそうです。そのサービスを使用するために、アプリのクラスとコンポーネントの相互作用を完全に理解する必要はありません。
解決策: 構成オブジェクトに委譲する私が見つけた最良の解決策は、オプション/構成データを独自の指定されたオブジェクトにカプセル化することです。このパターンを巧妙に説明すると、構成、設定、およびオプション データについては、継承や合成ではなく委任を使用します。
構成マッピングを構築する方法は、使用している言語によって異なります。多くの言語では、独自のConfig
オブジェクトを構築すると見栄えがよくなります。
if opts.verbose:
print "..."
プロパティにアクセスするより明示的な「ゲッター」opts.get("verbose")
または「インデクサー」の方法よりも読みやすいと思います。opts['verbose']
ただし、通常は独自のクラスを作成する必要はありませんConfig
。これは基本的に単なるマッピングです。
●簡単な方法● 一般的なマッピングを使用します。たとえば、Python a dict
、Perl a %hash
、Java aDictionary
または などHashMap
です。さらに良いことに、構成データ用に設計された、または構成データに特に適したこれらの拡張機能があります。Python では、たとえば、stufとTreeDictを単純なドット アクセスとその他の優れたプロパティに使用します。Java では、同様の構成固有の拡張機能です。例えば:Properties
from stuf import stuf # stuf has attributes!
opts = stuf(
show_module=False, # comment explaining what show_module means
where=True, # ...
truncate=False, # ...
everything=False, # ...
allvars=False, # ...
allkeys=False, # ...
yaml=False, # ...
on=True, # ...
ret=None, # ...
)
if opts.truncate:
...
このように、すべての構成データとオプション データは 1 か所にあり、きちんとアクセスでき、並べて使用される他のすべてのプログラム、クラス、インスタンス、および関数/メソッド データから明確に区別されます。これは、プログラムが進化するにつれて、時間の経過とともに明確さを維持するのに役立ちます。「これはコア データの一部なのか、それともコア データが処理されているコンテキストに関連しているのか」をすばやく判断できます。
さらに良いことに、構成ファイルから構成データをプリロードする場合は、それらの値を構成オブジェクトに直接ロードまたはコピーします。また、コマンド ラインから引数を取得する場合は、それらの値を構成オブジェクトに直接ロードまたはコピーします。これで、「ユーザーが私に何をしてほしいか、どのオプションと設定を使用するか」というすべての情報をまとめた 1 つのソースができました。情報。
TL;DR - アプリまたはサービスの 90% は、単純な構成/オプション マッピングで問題ありません。以下はすべて、高度なユースケース向けです。これは設計/パターンの問題であるため、このアプローチが 1 回限りではなく、より洗練された/複雑なユース ケースに拡張される理由を次に示します。
●インスタンスごとの構成● 複数のレベルの構成/オプション データを持つことができます。これの最も一般的な用途は、クラスまたはモジュール レベルで設定されたデフォルトであり、インスタンスごとに異なるオプションになる可能性があります。サーバー アプリにはユーザーごとにインスタンスがあり、各ユーザー/インスタンスには独自のカスタマイズされた設定が必要です。構成マップは、インスタンスの作成/初期化時に、自動的または明示的にコピーされます。
●●複数の構成オブジェクト●● 構成/オプション データを複数の構成オブジェクトに分割することができます。たとえば、データの書式設定のオプションからデータの取得のオプションを分割できます。これは設計の開始時に行うことができますが、必須ではありません。1 つのモノリシックな構成オブジェクトから始めて、時間をかけてリファクタリングします (通常は、基礎となる関数のリファクタリングを開始するときに)。明らかに、構成オブジェクトの追加に「夢中」になりたくはありませんが、プログラムをそれほど複雑にすることなく、いくつかの構成オブジェクトを持つことができます。構成オブジェクトを分割すると、単一の API を介して複数の構成「ドメイン」をプロキシできます。内部的には質の高い情報を分解できますが、外観は非常にシンプルになります。
◆チェーン ギャング◆ インスタンスごとに構成データをコピーするよりもエレガント: 連鎖可能または階層的なマッピング (Python などChainMap
) を使用して、あるマッピングの値を別のマッピングの値に「オーバーレイ」できるようにします (「コピー オン ライト」スキームに似ています)。 、または「ユニオン」および「半透明」ファイルシステム)。インスタンス オプションは、明示的に設定されていない限り、クラス/デフォルト オプションを直接参照します。明示的に設定されている場合は、インスタンス固有です。利点: プログラムの実行中にクラス/デフォルト/グローバル設定が変更された場合、以降のインスタンス メソッドの呼び出しでは、変更されたデフォルトが「認識」されて使用されます (インスタンス レベルでオーバーライドされていない限り)。
◆◆一時的な構成◆◆ 「オンザフライ」で変更可能な構成/オプションが必要な場合 (たとえば、特定のメソッド呼び出しのスコープについて)、メソッドはインスタンス オプション チェーン マッピングを拡張できます。Python では、それがChainMap.new_child()
機能します。複雑に聞こえますが、メソッド コードに関する限り、非常に単純です。参照する構成オブジェクトはまだ 1 つしかなく、それが示すオプションが何であれ、それを使用してください。
◆◆◆任意の期間のオーバーレイ◆◆◆ メソッド呼び出しの一時的な範囲について魔法のようなことは何もありません。適切にセットアップすれば、任意のレベルの構成を必要なだけ一時的にオーバーレイできます。たとえば、プログラムの実行中にデバッグ、ログ、またはプロファイリングをオンにしたい期間がある場合、好きなときにオンまたはオフにすることができます-特定のインスタンスのみ、またはそれらすべてを一度に. このオードブル カテゴリ の使用には、Config
在庫をわずかに超えるオブジェクトが必要ですが、ChainMap
それほど多くはありません (基本的には、チェーン マッピングへのハンドルのみ)。
幸いなことに、ほとんどのコードは、これらの「ブラック ダイヤモンド」レベルの洗練された構成を必要とするほどではありません。しかし、3 層または 4 層の深さまで行きたい場合は、個別の構成オブジェクトに委譲することで、コードをクリーンで整然とした状態に保つ論理的な方法でそこに到達できます。