3

私が設計しているクラス内で何が起こっているかを正確に伝える命名規則を考えようとしています。二次的な注意として、私は 2 つのほぼ同等のユーザー API のどちらかを決定しようとしています。

状況は次のとおりです。

中心的なデータ構造の 1 つに、1) 蓄積、2) 分析、3) クエリ実行の 3 つのフェーズがある科学アプリケーションを構築しています。

私の場合、これは空間モデリング構造であり、内部で KDTree を使用して 3 次元空間内のポイントのコレクションを分割します。各ポイントは、周囲環境の 1 つまたは複数の属性を表し、測定自体について一定レベルの信頼性があります。

コレクションに (場合によっては多数の) 測定値を追加した後、オブジェクトの所有者は、該当するフィールド内のどこかにある新しいデータ ポイントで内挿された測定値を取得するためにクエリを実行します。

API は次のようになります (コードは Java ですが、それほど重要ではありません。わかりやすくするために、コードは 3 つのセクションに分かれています)。

// SECTION 1:
// Create the aggregation object, and get the zillion objects to insert...
ContinuousScalarField field = new ContinuousScalarField();
Collection<Measurement> measurements = getMeasurementsFromSomewhere();

// SECTION 2:
// Add all of the zillion objects to the aggregation object...
// Each measurement contains its xyz location, the quantity being measured,
// and a numeric value for the measurement. For example, something like
// "68 degrees F, plus or minus 0.5, at point 1.23, 2.34, 3.45"
foreach (Measurement m : measurements) {
   field.add(m);
}

// SECTION 3:
// Now the user wants to ask the model questions about the interpolated
// state of the model. For example, "what's the interpolated temperature
// at point (3, 4, 5)
Point3d p = new Point3d(3, 4, 5);
Measurement result = field.interpolateAt(p);

私の特定の問題ドメインでは、SECTION 2 で少量の増分作業 (ポイントをバランスの取れた KDTree に分割する) を実行できます。

また、SECTION 3 で発生する可能性のある少量の作業 (いくつかの線形補間の実行) があります。

しかし、セクション 2 と 3の間で実行しなければならない膨大な量の作業 (テイラー級数とエルミート関数を使用して、カーネル密度推定器を構築し、高速ガウス変換を実行しますが、それはまったく的外れです) があります。

過去に、データ構造を構築するために遅延評価を使用したことがありました (この場合は、「interpolateAt」メソッドの最初の呼び出し時です)。しかし、ユーザーが「field.add」を呼び出すと、 ()" メソッドをもう一度使用すると、それらのデータ構造を完全に破棄して、最初からやり直す必要があります。

他のプロジェクトでは、ユーザーが明示的に「object.flip()」メソッドを呼び出して、「追加モード」から「クエリ モード」に切り替える必要がありました。このような設計の良いところは、ハードコア計算が開始される正確な瞬間をユーザーがより適切に制御できることです。しかし、API コンシューマがオブジェクトの現在のモードを追跡するのは面倒です。さらに、標準的な使用例では、クエリの発行を開始した後、呼び出し元がコレクションに別の値を追加することはありません。ほとんどの場合、データ集計はクエリの準備よりも完全に優先されます。

このようなデータ構造の設計をどのように処理しましたか?

新しいデータがコレクションに入ったときに中間データ構造を捨てて、オブジェクトにその重い分析を怠惰に実行させたいですか? それとも、データ構造を追加モードから照会モードに明示的に切り替えるようにプログラマーに要求しますか?

そして、このようなオブジェクトの命名規則を知っていますか? 私が考えていないパターンはありますか?


編集時:

この例で使用した「ContinuousScalarField」という名前のクラスについては、混乱と好奇心があるようです。

これらのウィキペディアのページを読むと、私が話していることのかなり良いアイデアを得ることができます:

地形図を作成したいとしましょう (これは私の正確な問題ではありませんが、概念的には非常に似ています)。したがって、1 平方マイルの領域で 1,000 の高度測定を行いますが、測量機器には標高でプラスまたはマイナス 10 メートルの誤差があります。

すべてのデータ ポイントを収集したら、値を補間するだけでなく、各測定のエラーも考慮に入れるモデルにそれらをフィードします。

トポ マップを描画するには、ピクセルを描画する各ポイントの標高をモデルにクエリします。

単一のクラスがクエリの追加と処理の両方を担当する必要があるかどうかという質問については、100% 確信はありませんが、そうだと思います。

同様の例を次に示します。HashMap クラスと TreeMap クラスを使用すると、オブジェクトの追加とクエリの両方を実行できます。追加とクエリのための個別のインターフェイスはありません。

クエリ メカニズムをサポートするために内部データ構造を継続的に維持する必要があるため、どちらのクラスも私の例に似ています。HashMap クラスは定期的に新しいメモリを割り当て、すべてのオブジェクトを再ハッシュし、オブジェクトを古いメモリから新しいメモリに移動する必要があります。TreeMap は、赤黒木データ構造を使用して、木のバランスを継続的に維持する必要があります。

唯一の違いは、データ セットが閉じていることがわかったら、すべての計算を実行できる場合に、クラスが最適に実行されることです。

4

6 に答える 6

4

オブジェクトにこのような 2 つのモードがある場合、2 つのインターフェイスをクライアントに公開することをお勧めします。オブジェクトが追加モードの場合は、クライアントが IAppendable 実装のみを使用できるようにします。クエリ モードに切り替えるには、AsQueryable などの IAppendable にメソッドを追加します。元に戻すには、IQueryable.AsAppendable を呼び出します。

同じオブジェクトに IAppendable と IQueryable を実装し、内部的に同じ方法で状態を追跡できますが、2 つのインターフェイスを使用すると、オブジェクトの状態がクライアントに明確になり、クライアントは意図的に (高価な) スイッチ。

于 2008-10-29T18:35:16.657 に答える
2

オブジェクトには、1 つの役割と責任が必要です。あなたの場合、ContinuousScalarField は補間を担当する必要がありますか?

おそらく、次のようなことをした方が良いかもしれません:

IInterpolator interpolator = field.GetInterpolator();
Measurement measurement = Interpolator.InterpolateAt(...);

これが理にかなっていることを願っていますが、問題のドメインを完全に理解していなければ、より一貫した答えを出すことは困難です.

于 2008-10-29T18:31:15.713 に答える
2

私は通常、結果を怠惰に再計算するよりも、明示的な変更を行うことを好みます。このアプローチにより、ユーティリティのパフォーマンスが予測しやすくなり、優れたユーザー エクスペリエンスを提供するために必要な作業量が削減されます。たとえば、これが UI で発生した場合、砂時計のポップアップなどについてどこで心配する必要がありますか? 一定時間ブロックし、バックグラウンド スレッドで実行する必要がある操作はどれですか?

つまり、1 つのインスタンスの状態を明示的に変更するのではなく、Builder パターンを使用して新しいオブジェクトを生成することをお勧めします。たとえば、各サンプルを追加するときに少量の作業を行うアグリゲーター オブジェクトがあるとします。次に、提案されたvoid flip()方法の代わりにInterpolator interpolator()、現在の集計のコピーを取得し、すべての面倒な計算を実行する方法があります。interpolateAtメソッドは、この新しい Interpolator オブジェクトになります。

使用パターンが保証されている場合は、作成したインターポレーターへの参照を保持して単純なキャッシュを実行し、複数の呼び出し元に返し、アグリゲーターが変更されたときにのみクリアすることができます。

この責任の分離は、より保守しやすく再利用可能なオブジェクト指向プログラムを生み出すのに役立ちます。Measurement要求されたときに を返すことができるオブジェクトPointは非常に抽象的であり、おそらく多くのクライアントが、より一般的なインターフェイスを実装する 1 つの戦略として Interpolator を使用できます。


あなたが追加したアナロジーは誤解を招くと思います。別のアナロジーを考えてみましょう:

Key[] data = new Key[...];
data[idx++] = new Key(...); /* Fast! */
...
Arrays.sort(data); /* Slow! */
...
boolean contains = Arrays.binarySearch(data, datum) >= 0; /* Fast! */

これはセットのように機能し、実際にはSet実装 (ハッシュ テーブルまたはバランス ツリーで実装) よりも優れたパフォーマンスを提供します。

バランスの取れたツリーは、挿入ソートの効率的な実装と見なすことができます。挿入するたびに、ツリーはソートされた状態になります。バランスの取れたツリーの予測可能な時間要件は、ソートのコストが、一部のクエリで発生するのではなく、各挿入に分散されるという事実によるものです。

ハッシュ テーブルを再ハッシュすると、パフォーマンスの一貫性が低下するため、特定のアプリケーション (おそらくリアルタイム マイクロコントローラー) には適していません。ただし、再ハッシュ操作でさえ、挿入およびクエリ操作のパターンではなく、テーブルの負荷率のみに依存します。

アナロジーを厳密に保持するには、追加するポイントごとにアグリゲーターを「並べ替える」(毛むくじゃらの計算を行う) 必要があります。しかし、それは非常にコストがかかるように思われ、ビルダーまたはファクトリ メソッド パターンにつながります。これにより、時間のかかる「並べ替え」操作の準備が必要な時期がクライアントに明確になります。

于 2008-10-29T18:56:05.367 に答える
1

「データ構造を構築するために遅延評価を使用しました」 --良い

「ユーザーが再び「field.add()」メソッドを呼び出した場合、それらのデータ構造を完全に破棄して、最初からやり直す必要があります。」--興味深い

「標準的な使用例では、呼び出し元は、クエリの発行を開始した後にコレクションに別の値を追加することはありません」-おっと、誤警報、実際には面白くありません.

lazy eval はユースケースに適合するため、そのまま使用してください。これは非常に頻繁に使用されるモデルです。非常に信頼性が高く、ほとんどのユース ケースに非常によく適合するからです。

これを再考する唯一の理由は、(a) ユース ケースの変更 (混合加算と補間)、または (b) パフォーマンスの最適化です。

ユースケースの変更はほとんどないため、補間を分割することによるパフォーマンスへの影響を考慮することができます。たとえば、アイドル時間中にいくつかの値を事前計算できますか? または、追加するたびに更新できる概要はありますか?

また、高度にステートフルな (そしてあまり意味のない)flipメソッドは、クラスのクライアントにとってあまり役に立ちません。ただし、補間を 2 つの部分に分割することは、依然として彼らにとって役立つ可能性があり、最適化と状態管理に役立ちます。

たとえば、補間を 2 つの方法に分けることができます。

public void interpolateAt( Point3d p );
public Measurement interpolatedMasurement();

これは、リレーショナル データベースの Open and Fetch パラダイムを借用しています。カーソルを開くと、多くの準備作業が行われ、クエリの実行が開始される可能性があります。最初の行をフェッチすると、すべての作業が行われるか、準備されたクエリが実行されるか、単に最初のバッファされた行がフェッチされます。あなたは本当に知りません。あなたはそれが2つの部分からなる操作であることだけを知っています. RDBMS 開発者は、必要に応じて自由に最適化できます。

于 2008-10-29T18:52:33.843 に答える
0

新しいデータがコレクションに入ったときに中間データ構造を捨てて、オブジェクトにその重い分析を怠惰に実行させたいですか? それとも、データ構造を追加モードから照会モードに明示的に切り替えるようにプログラマーに要求しますか?

私は、追加ごとに「もう少し作業を加える」ことで増分的に追加できるデータ構造を使用し、抽出ごとに「もう少し作業を加える」ことで必要なデータを増分的にプルできるデータ構造を使用することを好みます。

おそらく、領域の右上隅で「interpolate_at()」呼び出しを行う場合、その右上隅のポイントを含む計算を行うだけでよく、他の 3 つの象限を離れても問題はありません。新しい追加に「開く」。(再帰的な KDTree も同様です)。

残念ながら、それは常に可能であるとは限りません。データを追加する唯一の方法は、以前の中間結果と最終結果をすべて破棄し、すべてを最初から再計算することです。

私がデザインしたインターフェイスを使用する人々、特に私は人間であり、過ちを犯しがちです。だから私は、人々が特定の方法で物事を行うことを覚えなければならないオブジェクトを使用するのは好きではありません。そうしないと、物事がうまくいかなくなります。

オブジェクトからデータを取得する前に、オブジェクトが「計算後の状態」にある必要がある場合、つまり、interpolateAt() 関数が有効なデータを取得する前に、いくつかの「do_calculations()」関数を実行する必要がある場合は、interpolateAt() 関数を使用することをお勧めします。すでにその状態にあるかどうかを確認し、「do_calculations()」を実行して、必要に応じてオブジェクトの状態を更新し、期待した結果を返します。

このようなデータ構造を、データを「フリーズする」、データを「結晶化する」、「コンパイルする」、「データを不変のデータ構造に入れる」などと表現する人がいるのを時々耳にします。1 つの例は、(可変) StringBuilder または StringBuffer を (不変) String に変換することです。

ある種の分析では、事前にすべてのデータを取得する必要があり、すべてのデータが入力される前に補間値を引き出すと、間違った結果が得られると想像できます。その場合、interpolateAt() 呼び出しの後に "add_data()" 関数が (誤って) 呼び出された場合、"add_data()" 関数が失敗するか例外をスローするように設定することをお勧めします。

遅延評価された「 interpolated_point」オブジェクトを定義することを検討します。このオブジェクトは、データをすぐに評価するのではなく、将来その時点でのデータが必要になることをプログラムに伝えるだけです。コレクションは実際には凍結されていないので、何らかの「interpolated_point」オブジェクトから最初の実際の値が実際に抽出されるまで、さらにデータを追加し続けても問題ありません。これにより、「do_calculations()」関数が内部的にトリガーされ、物体。すべてのデータだけでなく、補間する必要があるすべてのポイントも事前に知っていれば、処理速度が向上する可能性があります。次に、補間されたポイントから「遠く」にあるデータを破棄し、「近く」の領域でのみ重い計算を行うことができます

他の種類の分析では、手持ちのデータで最善を尽くしますが、後でさらにデータが入ってきた場合は、その新しいデータを後の分析で使用したいと考えます。それを行う唯一の方法が、すべての中間結果を捨てて、すべてをゼロから再計算することである場合、それがあなたがしなければならないことです。(そして、毎回「clear_cache()」および「do_calculations()」関数を呼び出すことを覚えておく必要があるのではなく、オブジェクトがこれを自動的に処理するのが最善です)。

于 2012-11-21T17:07:01.787 に答える
-1

状態変数を持つことができます。STATE が SECTION-1 にある場合にのみ機能する、高レベルの処理を開始するためのメソッドを用意します。状態を SECTION-2 に設定し、計算が完了すると SECTION-3 に設定します。プログラムに特定の点を補間する要求がある場合、プログラムは状態が SECTION-3 であるかどうかを確認します。そうでない場合は、計算の開始を要求し、指定されたデータを補間します。

このようにして、両方を達成します。プログラムは、ポイントを補間する最初の要求で計算を実行しますが、それよりも前に実行するように要求することもできます。これは、たとえば、補間を要求する必要なく、夜通し計算を実行したい場合に便利です。

于 2008-10-29T18:35:52.100 に答える