私は最近、さまざまな測定デバイスを駆動するアプリケーションに取り組み始めました。
ユーザーはメジャーを開始する前に、そのパラメーターを設定します。
実際には、すべての測定タイプを考慮すると、50以上のパラメーターがあります。
ここでの問題は、すべての設定が他の設定に依存していることです。
さらに、一部の測定設定は、以前の測定結果と設定に依存します。
簡単に言うと、相互に関連し合うものがたくさんあります。
実際のパターンは、値が変更されるとすぐにeverythingを検証することです。それは(時間内に)かなりのコストがかかり、さらに多くのパラメーターを追加します。壊れます。
ObservableValuesを使用し、すべてのパラメーターが依存するすべての値に登録されるパターンを実装しようとします。
パラメータが他の参照メジャーに依存している場合、困難になりました。参照が変更された場合は、前の参照のリッスンを停止して、新しい参照のリッスンを開始する必要があります。
等...
その他の問題は、パターンに取り組み、より多くの機能(シリアル化など)があった場合、またはhelperクラス(ファクトリーなど)があった場合、50以上のパラメーターまたは関数を含む大きなファイルを構築することです。 。
それを行うための他の良いパターンやライブラリはありますか?
私はお勧めします:
大きな問題を小さな問題に分割して、それ自体で解決しようと思います。小さな問題は、それらが十分に単純であれば、特殊なコンポーネントによって簡単に解決できます。各コンポーネントは、その主な目的に焦点を合わせ続ける必要があります。
コンポーネントがしっかりと結合されていない場合、システムは必要に応じてスケーリングまたは変更できます。つまり、構築プロセスが複雑すぎる場合、1つではなく複数のツリービルダークラスを使用することが可能です。これを実現するために、追加のコンポーネント(tree-builder-factory)が追加されます。適切なツリービルダーを提供することにより、ツリーのビルド/更新イベントに反応します。
Parameterクラスを作成します(C#を想定):_class Parameter<T> { T Value; string Name; }
_
次に、同じグラフの参照としてパラメーター値と依存関係を含むノードを使用してグラフを作成します。
そして次に、パラメータに対して直接ではなく、グラフ全体に対してすべての操作を実行します:graph.SetValue(parameter, value)
またはgraph.SetValue(parameterName, value)
メディエーターを使用してパラメーターイベントの依存関係を分離することを検討できます。これは Event Aggregator と呼ばれることもあります。個々のパラメータークラスは、EAを介して直接イベントをパブリッシュおよびサブスクライブするように関連付けられています。
次の例では Reactive Extensions を使用していますが、サブスクリプションとパブリッシングのコールバックを使用して、簡単なバージョンを自分で実装することもできます。
public class EventAggregator
{
private Subject<Event> _events;
public IObservable<Event> Events { get { return _events; } }
public void Publish(Event evt)
{
_events.OnNext(evt);
}
}
public class Parameter<T> : IDisposable
{
private readonly EventAggregator _ea;
public Parameter(EventAggregator ea, string name, T initialValue)
{
this._ea = ea;
this._name = name;
this._value = initialValue;
this._subscription = ea.Events.Subscribe(evt => HandleEvent(evt));
}
private readonly string _name;
public string Name { get { return this._name; } }
private T _value;
public T Value
{
get { return this._value; }
set
{
this._value = value;
this._ea.Publish(new ValueChangedEvent(this._name, this._value);
}
}
private void HandleEvent(Event evt)
{
// decide what to do with an event here - change availability of the parameter etc
}
public void Dispose()
{
this._subscription.Dispose();
}
}
単一のEventAggregator
を作成し、依存関係としてParameter
オブジェクトに渡します。
var eventAggregator = new EventAggregator();
var length = new Parameter<int>(eventAggregator, "Length", 50);
var width = new Parameter<int>(eventAggregator, "Width", 50);
// this will fire an event to the event aggregator which can be handled by other parameters
width.Value = 20;
// this unsubscribes from the event aggregator
length.Dispose();
複雑さの管理に役立つツールがあります。たとえば Feature Model アプローチを参照してください。
しかし、私がそれをしなければならなかった場合、私は制約プログラミングアプローチを試みます(機能ツリーは基本的に制約の簡略化された表記です)。 C#を使用しているので、 Prolog.NET を紹介します。ただし、既にPrologを使用していても、Prolog.Netについては特に知りません。
注:可能な場合は、この回答を例を使用して後で編集します(今はあまり時間はありません)。ちなみに、いくつか提供できるのであれば、yourの例のいずれかを使用したいと思います。
このような相互接続されたパラメーターの更新に取り組むには、さまざまな方法があります。私はこれまでに2つの経験があります。この回答の目的で、それらを「集中型」アプローチと「分散型」アプローチと呼びます。
一元化されたアプローチでは、単一の「UpdateEverything」関数を使用します。これは、システム全体で何かを実行するたびに、更新を要求する可能性が少しでもあることを確認する必要があります。一元化されたアプローチの利点は、シンプルであることです。更新を実装するすべてのコードが1か所にあるため、コードを読んで正しいことを確認できます。一元化されたアプローチの欠点は、a)通常、「UpdateEverything」関数は非常に長くなる、b)変更された可能性があるため、呼び出されるたびにすべての更新を実行する物事は本当に更新する必要があるかもしれません、そしてc)更新を見逃す可能性を減らすために、過度に呼び出される傾向があるので、全体として、それは巨大な不必要なオーバーヘッドを表します。
分散型アプローチとは、すでに作業を開始しているとおっしゃっていたとおりです。それはもっと複雑で、実際に使用する前にいくつかのフレームワークを構築する必要があります。このアプローチでは、各パラメーターは「変更された」通知をトリガーすることができ、各パラメーターは、それが依存するパラメーターのすべての1つを認識しているため、パラメーターが変更されたときに通知を受けるように登録して再計算できます。独自の値。おそらく独自の「変更された」通知を順番にトリガーします。分散型アプローチは、集中型アプローチのすべての問題を解決しますが、より複雑であり、そのため実装コストが高くなります。
注意すべきいくつかの点:
「GetValue()」と「RegisterForChangedEvent()」はあるが「SetValue()」はない「ReadonlyParameter」インターフェースを導入して主に使用する必要があります。これは、値を設定する意味がないためです。他のパラメーターの値に基づいて独自の値を再計算するために構築されたパラメーター。このコンテキストでの「読み取り専用」は、その値が決して変更されないことを意味するのではなく、単にその値を変更できないことを意味しますが、その値は他の理由でまだ変更される可能性があるため、順番に登録することは理にかなっていますそのような変化を観察するため。
外部から値を受け取るパラメーターについては、独自の値を「格納」(含む)する「StoringParameter」クラスを導入し、「ReadonlyParameter」インターフェースを実装し、さらに「SetValue()」メソッドを提供する必要がある場合があります。
独自の値を計算する方法を知っているパラメーターは、その値をキャッシュする必要があるため、a)コストのかかる再計算なしで「GetValue()」に応答でき、b)キャッシュされた値が変更されたときにのみ「変更」通知を発行します。 、不要な更新を排除するため。
独自の値を計算する方法を知っているパラメーターには、通常、呼び出されるプライベートRecomputeOwnValue()
メソッドがあります。a)依存するパラメーターの1つが変更されるたびに、b)独自のコンストラクターの最後に1回、その初期値を計算するように。パラメータが特定のパラメータに依存して停止し、代わりに別のパラメータに依存して開始する必要がある可能性がある場合は、依存するパラメータが追加/削除されるたびにRecomputeOwnValue()
も呼び出す必要があります。
頻繁に使用する操作のパラメータの代数全体を作成できます。たとえば、論理AND、OR、XOR etcパラメータクラスを導入できます。これらは、他の2つのブールパラメータの値に対する論理演算の結果として、独自の値を再計算する方法を知っています。依存しているので、算術演算パラメータクラスを導入したり、加算、減算、乗算、除算などを行うことができます。
式を書くのと同じくらい便利な方法で相互接続されたパラメーターをインスタンス化する単純なヘルパー関数を書くことができます。だから、あなたはこのようなものを持つかもしれません:
d = fob(a、fiddle(b、c)));
ここで、fob()
はFobParameter
オブジェクトを作成し、fiddle()
はFiddleParameter
オブジェクトを作成するため、上記の式はパラメータd
がnew FobParameter()
は、たまたまReadonlyParameter
の2つのインスタンスに依存します。このインスタンスには、既存のパラメーターa
とnew FiddleParameter()
を渡します。たまたまReadonlyParameter
の別の2つのインスタンスに依存しており、そのために既存のパラメーターb
とc
を渡します。
もう1つのアイデア:
次のような大きなマップにすべてのパラメーターを保存します。
class Params
{
Map<String, Object> map;
/* fancy helper methods... */
}
一連のルールオブジェクトを追加します。
interface Rule
{
/* returns "OK" or an error message
* or use exceptions or whatever you fancy */
void validate(Params params);
/* returns the list of params that will trigger the rule */
String[] triggers();
}
class FancyRule implements Rule
{
String validate(Params params) {
if( params.get("foo") <= params.get("bar") )
return "foo should be greater than bar";
else
return "OK";
}
String[] triggers() {
return ["foo", "bar"];
}
}
最後のステップとして、物事を一緒にバンドルする必要があります:
class Params
{
Map<String, Object> map;
void setParam(key,value) {
map.put(key,value); // + keep a backup if desired
// now validate everything depending on it
for( rule in rules having key as trigger):
if( rule.validate(this) != "OK" )
// ...
}
}