注- 元の投稿を一番下に移動しました。これは、このスレッドの新規参入者にとってまだ価値があると思うからです。すぐ下に続くのは、フィードバックに基づいて質問を書き直す試みです。
完全に編集された投稿
わかりました、私は特定の問題についてもう少し詳しく説明しようとします。ドメインロジックとインターフェイス/プレゼンテーションロジックを少しブレンドしていることに気づきましたが、正直なところ、どこで分離するかわかりません。我慢してください:)
私は、(とりわけ)物を移動するための物流シミュレーションを実行するアプリケーションを作成しています。基本的な考え方は、ユーザーが Visual Studio に似たプロジェクトを表示し、ここで概説しようとしているさまざまなオブジェクトを追加、削除、名前付け、整理、注釈付けなどを行うことができるということです。
項目と場所は、基本的な動作のないデータ項目です。
class Item { ... } class Location { ... }
WorldStateは、アイテムと場所のペアのコレクションです。WorldState は変更可能です。ユーザーはアイテムを追加および削除したり、場所を変更したりできます。
class WorldState : ICollection<Tuple<Item,Location>> { }
プランは、希望する時間に別の場所にアイテムを移動することを表します。これらは、プロジェクトにインポートするか、プログラム内で生成できます。WorldState を参照して、さまざまなオブジェクトの初期位置を取得します。プランも変更可能です。
class Plan : IList<Tuple<Item,Location,DateTime>> { WorldState StartState { get; } }
次に、シミュレーションがプランを実行します。それは多くのかなり複雑な動作やその他のオブジェクトをカプセル化しますが、最終的な結果はSimulationResult です。これは基本的に、このコストと計画がどれだけうまく達成されたかを説明するメトリックのセットです (プロジェクト トライアングルを考えてください)。
class Simulation { public SimulationResult Execute(Plan plan); } class SimulationResult { public Plan Plan { get; } }
基本的な考え方は、ユーザーがこれらのオブジェクトを作成し、それらを結び付け、再利用できる可能性があるということです。WorldState は、複数の Plan オブジェクトで使用できます。その後、シミュレーションは複数のプランで実行できます。
恐ろしく冗長になるリスクを冒して、例
var bicycle = new Item();
var surfboard = new Item();
var football = new Item();
var hat = new Item();
var myHouse = new Location();
var theBeach = new Location();
var thePark = new Location();
var stuffAtMyHouse = new WorldState( new Dictionary<Item, Location>() {
{ hat, myHouse },
{ bicycle, myHouse },
{ surfboard, myHouse },
{ football, myHouse },
};
var gotoTheBeach = new Plan(StartState: stuffAtMyHouse , Plan : new [] {
new [] { surfboard, theBeach, 1/1/2010 10AM }, // go surfing
new [] { surfboard, myHouse, 1/1/2010 5PM }, // come home
});
var gotoThePark = new Plan(StartState: stuffAtMyHouse , Plan : new [] {
new [] { football, thePark, 1/1/2010 10AM }, // play footy in the park
new [] { football, myHouse, 1/1/2010 5PM }, // come home
});
var bigDayOut = new Plan(StartState: stuffAtMyHouse , Plan : new [] {
new [] { bicycle, theBeach, 1/1/2010 10AM }, // cycle to the beach to go surfing
new [] { surfboard, theBeach, 1/1/2010 10AM },
new [] { bicycle, thePark, 1/1/2010 1PM }, // stop by park on way home
new [] { surfboard, thePark, 1/1/2010 1PM },
new [] { bicycle, myHouse, 1/1/2010 1PM }, // head home
new [] { surfboard, myHouse, 1/1/2010 1PM },
});
var s1 = new Simulation(...);
var s2 = new Simulation(...);
var s3 = new Simulation(...);
IEnumerable<SimulationResult> results =
from simulation in new[] {s1, s2}
from plan in new[] {gotoTheBeach, gotoThePark, bigDayOut}
select simulation.Execute(plan);
問題は、次のようなものが実行されるときです。
stuffAtMyHouse.RemoveItem(hat); // this is fine
stuffAtMyHouse.RemoveItem(bicycle); // BAD! bicycle is used in bigDayOut,
したがって、基本的に、ユーザーが呼び出しを介して WorldState (およびおそらくプロジェクト全体) から項目を削除しようとするworld.RemoveItem(item)
と、その項目がその WorldState を使用する Plan オブジェクトで参照されないようにする必要があります。もしそうなら、私はユーザーに「おい!次のプランXがこのアイテムを使用している!それを削除しようとする前にそれを処理してください!」と伝えたい. 通話で望ましくない種類の動作は次のとおりです。world.RemoveItem(item)
- アイテムを削除しますが、プランはそれを参照します。
- アイテムを削除するが、Plan にアイテムを参照するリスト内のすべての要素をサイレント モードで削除させる。(実際には、これはおそらく望ましいことですが、二次的なオプションとしてのみ使用できます)。
したがって、私の質問は基本的に、そのような望ましい動作をきれいに分離した方法で実装するにはどうすればよいかということです。これをユーザーインターフェイスの範囲にすることを検討しました(したがって、ユーザーがアイテムで「del」を押すと、プランオブジェクトのスキャンがトリガーされ、world.RemoveItem(item)を呼び出す前にチェックが実行されます)-しかし(a)私はまた、ユーザーがworld.RemoveItem(item)
自分自身を呼び出すことができるように、カスタム スクリプトを作成および実行できるようにしています。(b) この動作が純粋に「ユーザー インターフェイス」の問題であるとは確信していません。
ふぅ。まあ、誰かがまだ読んでいるといいのですが...
元の投稿
次のクラスがあるとします。
public class Starport
{
public string Name { get; set; }
public double MaximumShipSize { get; set; }
}
public class Spaceship
{
public readonly double Size;
public Starport Home;
}
したがって、宇宙船のサイズがそのホームの MaximumShipSize 以下でなければならないという制約が存在するとします。
では、これにどのように対処すればよいでしょうか。
伝統的に、私は次のように結合された何かをしました:
partial class Starport
{
public HashSet<Spaceship> ShipsCallingMeHome; // assume this gets maintained properly
private double _maximumShipSize;
public double MaximumShipSize
{
get { return _maximumShipSize; }
set
{
if (value == _maximumShipSize) return;
foreach (var ship in ShipsCallingMeHome)
if (value > ship)
throw new ArgumentException();
_maximumShipSize = value
}
}
}
これは、このような単純な例では扱いやすいものですが (おそらく悪い例です)、制約が大きくなり、より複雑になるにつれて、より関連する機能が必要になることがわかりました (たとえば、メソッドを実装するbool CanChangeMaximumShipSizeTo(double)
か、データを収集する追加のメソッドを実装する不要な双方向の関係 (この場合、SpaceBase-Spaceship がほぼ間違いなく適切です) と複雑なコードを書くことになり、所有者側とはほとんど関係がありません。
では、この種のことは通常どのように処理されますか? 私が検討したこと:
ComponentModel INotifyPropertyChanging/PropertyChanging パターンに似たイベントを使用することを検討しましたが、EventArgs にはある種の Veto() または Error() 機能があります (winforms でキーを消費したり、フォームの終了を抑制したりできるように)。しかし、これがイベンティングの悪用に該当するかどうかはわかりません。
または、明示的に定義されたインターフェースを介して自分でイベントを管理します。
asdf ここにこの行が必要です。そうしないと、フォーマットが機能しません
interface IStarportInterceptor
{
bool RequestChangeMaximumShipSize(double newValue);
void NotifyChangeMaximumShipSize(double newValue);
}
partial class Starport
{
public HashSet<ISpacebaseInterceptor> interceptors; // assume this gets maintained properly
private double _maximumShipSize;
public double MaximumShipSize
{
get { return _maximumShipSize; }
set
{
if (value == _maximumShipSize) return;
foreach (var interceptor in interceptors)
if (!RequestChangeMaximumShipSize(value))
throw new ArgumentException();
_maximumShipSize = value;
foreach (var interceptor in interceptors)
NotifyChangeMaximumShipSize(value);
}
}
}
しかし、これがより良いかどうかはわかりません。また、この方法で自分のイベントをローリングするとパフォーマンスに影響があるかどうか、またはこれが良い/悪い考えである理由が他にあるのかどうかもわかりません。
3 番目の選択肢は、PostSharp または IoC/依存性注入コンテナーを使用する非常に風変わりな aop です。私はまだその道を進む準備ができていません。
すべてのチェックなどを管理する神オブジェクト -神オブジェクトのスタックオーバーフローを検索するだけで、これは悪くて間違っているという印象を受けます
私の主な懸念は、これはかなり明白な問題のように思え、かなり一般的な問題だと思っていましたが、それについての議論を見たことがありません (たとえば、System.ComponentModel は PropertyChanging イベントを拒否する機能を提供していません。そうですか?); これは、私が (もう一度) カップリングまたは (さらに悪いことに) 一般的なオブジェクト指向設計のいくつかの基本的な概念を把握できなかったのではないかと心配しています。
コメント? }