_class XFactory
_のオブジェクトを作成するファクトリ_class X
_があります。 X
のインスタンスは非常に大きいため、ファクトリの主な目的は、クライアントコードに対してできるだけ透過的にインスタンスをキャッシュすることです。 _class X
_のオブジェクトは不変であるため、次のコードは妥当なようです。
_# module xfactory.py
import x
class XFactory:
_registry = {}
def get_x(self, arg1, arg2, use_cache = True):
if use_cache:
hash_id = hash((arg1, arg2))
if hash_id in _registry:
return _registry[hash_id]
obj = x.X(arg1, arg2)
_registry[hash_id] = obj
return obj
# module x.py
class X:
# ...
_
それは良いパターンですか? (実際のファクトリーパターンではないことはわかっています。)変更すべき点はありますか?
現在、X
オブジェクトをディスクにキャッシュしたい場合があります。そのためにpickle
を使用し、オブジェクトへの参照の代わりに、ピクル化したオブジェクトのファイル名を__registry
_に値として保存します。もちろん、__registry
_自体は永続的に保存する必要があります(おそらく、独自のピクルファイル、テキストファイル、データベース、または単純にピクルファイルに_hash_id
_を含むファイル名を与えることによって)。 。
現在は、キャッシュされたオブジェクトの有効性は、get_x()
に渡されるパラメーターだけでなく、これらのオブジェクトを作成したコードのバージョンにも依存しています。
厳密に言えば、誰かが_x.py
_またはその依存関係を変更し、プログラムの実行中に再ロードすると、メモリキャッシュオブジェクトでも無効になる可能性があります。これまでのところ、この危険性は無視しました。自分のアプリケーションにとってはありそうにないことです。ただし、オブジェクトが永続ストレージにキャッシュされている場合は、無視できません。
私に何ができる?引数_hash_id
_と_arg1
_、および_arg2
_のファイル名と最終更新日を含むタプルのハッシュを計算することにより、_x.py
_をより堅牢にすることができると思います(再帰的に)依存するすべてのモジュールとデータファイル二度と役に立たなくなるキャッシュファイルを削除するために、各レコードの変更された日付のハッシュされていない表現を__registry
_に追加します。
しかし、理論的には誰かが動的にモジュールをロードする可能性があるため、このソリューションでさえ100%安全ではありません。ソースコードを静的に分析することからは、私はそれについて知りません。私がすべて出て、プロジェクト内のすべてのファイルが依存関係であると想定した場合、一部のモジュールが外部のWebサイトなどからデータを取得すると、メカニズムはまだ壊れます。
さらに、_x.py
_とその依存関係の変更の頻度が非常に高いため、キャッシュの無効化が頻繁に発生します。
したがって、ある程度の安全性を放棄し、明らかな不一致がある場合にのみキャッシュを無効にすることも考えました。つまり、_class X
_には、クラスレベルのキャッシュ検証識別子があり、開発者がキャッシュを無効にする変更が発生したと信じている場合は常に変更する必要があります。 (複数の開発者がいる場合、それぞれに個別の無効化識別子が必要です。)この識別子は_arg1
_および_arg2
_とともにハッシュされ、__registry
_に格納されているハッシュキーの一部になります。
開発者は検証識別子を更新するのを忘れるか、既存のキャッシュが無効化されていることに気付かない可能性があるため、別の検証メカニズムを追加するほうがよいように思われます。_class X
_は、X
。たとえば、X
がテーブルの場合、すべての列の名前を追加します。ハッシュ計算には、特性も含まれます。
このコードを書くことはできますが、重要な何かが欠けていると思います。また、このすべてをすでに実行できるフレームワークまたはパッケージがあるかどうかも疑問に思っています。インメモリキャッシングとディスクベースキャッシングを組み合わせるのが理想的です。
編集:
私のニーズはプールパターンによって十分に満たすことができるように思えるかもしれません。しかし、さらに調査すると、そうではありません。私は違いを挙げようと思いました:
オブジェクトは複数のクライアントで使用できますか?
プールサイズを制御する必要がありますか?
すべてのオブジェクトは自由に置き換え可能ですか?
パフォーマンスへの影響は注意深い分析が必要ですか?
オブジェクトはいつ破壊されますか?
マルチスレッド環境にはどのような特別な考慮事項がありますか?
クライアントがオブジェクトを解放しない場合、何をする必要がありますか?
オブジェクトを変更する必要がありますか?
オブジェクトの永続化に関連する特別な考慮事項はありますか?
要約すると、プールの複雑さはアイテム1、2、4、6、およびおそらく5、7、8にあります。XFactoryの複雑度はアイテム3、6、9にあります。唯一のオーバーラップはアイテム6であり、コアではありませんプールまたはXFactoryのいずれかの機能ですが、マルチスレッド環境で機能する必要があるすべてのパターンに共通する設計上の制約です。
あなたの懸念は非常に有効であり、元の簡単なキャッシングソリューションが最終的にアーキテクチャの一部になり、自然に新しいレベルの問題を引き起こしていることがわかります。
キャッシングに対する優れたアーキテクチャソリューションは、注釈をIoCと組み合わせて使用し、説明したいくつかの問題を解決することです。例えば:
私のプロジェクト(JavaまたはC#)では、Springキャッシングアノテーションを使用しています。簡単な説明 ここ を見つけることができます。
IoC は、必要に応じてキャッシュシステムを構成できるため、このソリューションの重要な概念です。
Pythonに同様のソリューションを実装するには、注釈を使用する方法を見つけ、プロキシを構築できるIoCコンテナを検索する必要があります。それが、すべてのメソッドをインターセプトするために注釈が機能する方法です。キャッシングのためのこの特定のソリューションを呼び出して提供します。
キャッシュの見方は良いです-Xはそうではありません。
単一インスタンスのIMHO逆シリアル化は、キャッシュの問題ではありません。それは相応のクラスのための仕事です。ここでの主な問題は、このクラスが頻繁に変更されることです。インスタンスをキャッシュすることの懸念とオブジェクトを逆シリアル化することの懸念を分離することをお勧めします。後者は、Xが古い形式も逆シリアル化できるように改善する必要があります。これは非常にトリッキーで高価な場合があります。それが高すぎる場合、Xが頻繁に変更される限り、古いバージョンをロードする必要があるかどうかを自問する必要がありますか?
ところで、バージョン識別子は必須のようです。 Xの構造についてのさらなる知識がなければ、私は推測しかできませんが、Xの構造は論理的にモジュール化されているようです(たとえば、特性について話しました)。もしそうなら、多分それはこの構造を明示的にすることを助けるでしょう。