OK、ここで Java 言語を使用して問題へのアプローチを説明しようと思います。
SumExpression (他の 2 つの式の結果を加算するために使用される式) の例ですべてを説明します。
ユーザーコード
私は、最も単純なアプローチである Observer パターンから始めました。すべての式は、キャッシュの無効化のために依存関係をリッスンします。この方法で実装された SumExpression のバージョンを次に示します。
public class SumExpression implements Expression<Integer> {
private final Expression<Integer> a;
private final Expression<Integer> b;
Integer value;
private Listener invalidator = new Listener() {
@Override
public void changed() {
invalidate();
}
};
public SumExpression(SimpleVariable<Integer> a, SimpleVariable<Integer> b) {
this.a = a;
this.b = b;
a.listeners().addListener(invalidator);// don't forget to call it!
b.listeners().addListener(invalidator);
}
public Integer getValue()
{
validate();
return value;
}
private void validate() {
if(value == null)
value = evaluate;
}
private void evaluate() {
value = null;
}
public void dispose() { // USER, DON'T FORGET TO CALL IT!!!
a.removeListener(invalidator);
b.removeListener(invalidator);
}
ListenerCollection listeners = new ListenerCollection();
@Override
public void addListener(Listener l) {
listeners.addListener(l);
}
@Override
public void removeListener(Listener l) {
listeners.removeListener(l);
}
}
ただし、うまくいかない可能性がある場所はたくさんあります。2 つの数値の足し算のような単純なものは、はるかに単純なはずです。そのため、次の方法でキャッシュからロジックを切り離しました。
public class SumExpression implements Expression<Integer> {
private final Expression<Integer> a;
private final Expression<Integer> b;
public SumExpression(Expression<Integer> a, Expression<Integer> b)
{
this.a = a;
this.b = b;
}
public Integer evaluate(EvaluationContext context)
{
return context.getValue(a)+context.getValue(b);
}
}
はるかに簡単ですね。ここでEvaluationContext
の役割は 2 つあることに注意してください。キャッシュから値を取得し、SumExpression
と 式a
との間の依存関係のリストを収集しb
ます。
コアコード
次に、EvaluationContext
キャッシュされたデータを と同様の構造で格納するグローバル キャッシング クラスとWeakHashMap<Expression, Object>
、タイプが のノードを持つ DAG のディペンデンシー グラフ データを提供しWeakReference<Expression>
ます。
これが私のevalとupdateの実装です:
public <T1> T1 eval(final Expression<T1> expression)
{
Weak weak = weaken(expression);
T1 result = (T1) cache.get(weak);
if(result == null) {
result = expression.evaluate(new EvaluationContext()
{
@Override
public <T2> T2 getValue(Expression<T2> dependency) {
registerDependency(expression, dependency);
return eval(dependency);
}
});
cache.put(weak, result);
}
return result;
}
public void update(Expression<?> ex) {
changed(weaken(ex));
}
public void changed(Weak weak) {
cache.remove(weak);
dependencies.removeOutgoingArcs(weak);
for(Weak dependant : new ArrayList<Weak>(dependencies.getIncoming(weak))) {
changed(dependant);
}
}
キャッシュ マネージャーがオブジェクトを要求されると、最初にキャッシュをチェックします。キャッシュに値がない場合は、式に評価を求めます。次に式は、getValue() メソッドを呼び出して依存関係を解決するようにキャッシュ マネージャーに要求します。これにより、ディペンデンシー グラフに円弧が作成されます。このグラフは、後でキャッシュの無効化に使用されます。
式が無効化されると、依存関係グラフが調査され、すべての依存キャッシュが無効化されます。
キャッシュと依存グラフのクリーンアップは、ガベージ コレクターが (ReferenceQueue を介して) 一部の式オブジェクトの終了を通知するとすぐに実行されます。
ほとんどすべてが正常に機能します。ただし、いくつかのトリッキーなケースがあります。
トリッキーなケース
最初のケースは、ぶら下がっている中間依存関係です。次のクラスがあるとします。
class SumProxyExpression implements Expression<Integer> {
private final Expression<Integer> a;
private final Expression<Integer> b;
public SumProxyExpression(Expression<Integer> a, Expression<Integer> b) {
this.a = a;
this.b = b;
}
@Override
public Integer evaluate(EvaluationContext context) {
Expression<Integer> s = new SumExpression(a, b);
return context.getValue(s);
}
}
のインスタンスを作成し、後でc=SumProxyExpression(a,b)
値を変更する場合は、その値も変更する必要があります。ただし、中間体がすでにガベージ コレクションされている場合、これは発生しない可能性があります。これに対抗するために、ノードがリーフ ノードでない限り、ディペンデンシー グラフからノードを削除しません (着信アークのみまたは発信アークのみを持つ)。a
c
SumExpression
解決方法がわからないもう1つのケースは次のとおりです。
class SelfReferencingExpression implements Expression<List<?>> {
class Result extends ArrayList<Integer> {
}
@Override
public List<?> evaluate(EvaluationContext resolver) {
return new Result();
}
}
そのような式の結果をキャッシュすると、キャッシュされた値 ( Result
) へのハード参照を保持し、それを含むクラス (式) への参照があるため、ガベージ コレクションされることはありません。したがって、式は常に到達可能ですが、使用できませんでした。
これはメモリ リークであり、それを解消する方法がわかりません。そのような参照を決して持たないようにユーザーに伝えることは可能ですが、非常に危険なので、より良い解決策を見つけたいと思います.
代替ソリューション
また、すべてをグローバル キャッシュに保持するのではなく、共通のセルフ キャッシュ式クラスから継承して実装することも考えました。このソリューションは、最後のテスト ケース (SelfReferencingExpression) を解決しますが、最初のテスト ケース (SumProxyExpression) では失敗します。だから、私は何をすべきかわかりません。助けてください。