C#で多次元ポイントクラスをモデル化しようとしています。ポイントは8種類くらいありますが、今後さらに増えるかもしれません。現在、私はすべての一般的な動作とデータを保持するスーパークラス(PointBase
)を持っています。このデータには、double、string、enum、DateTimesなどの単純なプロパティのみが含まれます(これらはディメンションです)。サブクラス(たとえば、1Point
、2Point
など)はすべて、少なくとも次のものをオーバーライドします。これらのディメンションの2つを計算するための動作を持つメソッド。この動作が唯一の変化するものである場合、私はStrategy + Factoryパターンを使用してそれで済ますことができます(正直なところ、おそらくこれら2つの単純な一般的な動作を後でリファクタリングします)。
残念ながら、いくつかのポイントは、タイプに基づいて、またはスーパークラスの他のプロパティの値に基づいて、1つまたは2つのプロパティを追加するので行き詰まっています。たとえば、ポイントタイプ1〜7の場合、追加のBreakeven
プロパティは必要ありませんが、タイプ8の場合は必要です(これは問題#1です)。
さらに(これは問題#2です)、Product
プロパティ(これは存在し、スーパークラスで設定され、列挙型です)に応じて、Term
enumプロパティを追加する必要がある場合がありますそれはすべてのサブクラスポイントに適用されます。ただし、私のProduct
列挙値の1つだけに用語が必要です。他の人はしません。
追加のパブリックプロパティを非表示にするList<PointBase>
データ構造にこれらのポイントのリストを格納する必要があるため、この状況は複雑になります。各PointBase
を1Point
または2Point
またはそれを取り出したときにキャストする必要があり、これは(もちろん)いくつかの設計原則(分離、少なくとも、私は思う)。
残念ながら、これらの異なるポイントを同じWPF DataGridに表示しようとしています(ユーザーが1Point
または2Point
などを選択して、ポイントのパブリックプロパティをDataGrid)。これまでのところ、このコードはすべてモデル内(別のDLL)に含まれていますが、DataGridのビューでこのポリモーフィズムをどのように表すかについてもわかりません。 DataGridがポイントのパブリックプロパティに基づいて列を自動生成するだけで十分ですが、これらのポイントをList<PointBase>
で渡すと、追加する必要のある追加のプロパティを取得する方法がなくなります。 。代わりに、追加のプロパティを含める必要があるたびに文字列キーと文字列値を追加する、CustomAttributesと呼ばれるスーパークラスに仮想ディクショナリを追加することを考えました。ただし、DataGridのディクショナリをフラット化するのは非常に難しいため、これは非常に煩わしいものです。これが唯一の方法だと心配しています...
私が知っていること(ソフトウェアエンジニアリングには制限がある)から、私はこのすべてのポリモーピズムをキャプチャするための抽象的なファクトリのようなものが必要です。しかし、正確にはわかりません。要約すると、私は必要です:
8Point
サブクラスの場合、Breakeven
プロパティを追加する必要がありますが、このプロパティは他のサブクラスでは意味がありません。Product
(スーパークラスで設定)がA
の場合、Term
という名前のすべてのサブクラスに追加のプロパティを追加する必要があります。このプロパティは、他の製品には適用されないか、意味がありません。私はここで継承と結婚していません(実際には、可能な場合は委任+インターフェイスなど、よりエレガントで簡単な方法を実行したいと思っています)。前もって感謝します!
編集:もう少しコンテキストを与えるために、ここに私のセットアップが今どのように見えるかがあります:
public abstract class StatisticPointBase
{
public DateTime EventTime;
public Product Product; // an enum
protected double _delta;
public string Name;
public double FooSensitivity;
public double BarSensitivity;
public double BazSensitivity;
public double FlopRatio;
public double FlapCoefficient;
...
public double Value; // set by overridden CalculateValue
public double Velocity; // set by overridden CalculateVelocity
public PointBase(IDataRepository d, string name, double delta, Product p)
{
Name = name; // just the name, like "Alpha" or "Beta"
_delta = delta; // follows name, e.g. for Alpha it's always 0.63
Product = p;
FooSensitivity = d.GetFooSensitivity(p);
... // other setup
}
public abstract double CalculateValue();
public abstract double CalculateVelocity();
}
public class AlphaStatisticPoint
{
public SimpleStatisticPoint()
{
Value = CalculateValue();
Velocity = CalculateVelocity();
}
override double CalculateValue()
{
return (fooSensitivity + barSensitivity + bazSensitivity) * Velocity / 2;
}
override double CalculateVelocity()
{
return FlopRatio * (_delta / FlapCoefficient);
}
}
public class BetaStatisticPoint
{
public SimpleStatisticPoint()
{
Value = CalculateValue();
Velocity = CalculateVelocity();
}
override double CalculateVelocity()
{
return (FlopRatio * 100) * (_delta);
}
override double CalculateValue()
{
return fooSensitivity * Velocity;
}
}
// other points...
public class ZetaStatisticPoint
{
public double Breakeven;
public SimpleStatisticPoint()
{
Value = CalculateValue();
Velocity = CalculateVelocity();
Breakeven = CalculateBreakeven();
}
override double CalculateVelocity()
{
return (FlopRatio * 100) * (_delta);
}
override double CalculateValue()
{
return fooSensitivity * Velocity;
}
override double CalculateBreakeven()
{
return 2 * Math.sqrt(fooSensitivity / barSensitivity);
// This is not defined for other types of statistics, and is only relevant for Zeta-type statistics.
// For other statistics, we can't even calculate the breakeven; there's no formula for it.
}
}
public enum Product
{
Widget,
Watchet,
/* Woffet will be added in future, and needs a Term */
}
Name
やDelta
のようなものは、各サブクラスに1つあり、各値は各サブクラスに一意であるため、サブクラスで設定する必要があります。たとえば、Alphaのデルタ値は常に0.63、Zetaは常に0.21などです。ただし、継承を回避しようとしていますが、実際の問題は、Breakeven
がZetaにのみ存在することです(これは問題#1です)。 )。
さらに(これが問題#2です)、Term
プロパティを作成して設定する必要があるかもしれませんが、Product
値がWoffet
のポイントに対してのみです。他の製品には意味がありません。このプロパティTerm
はすべてのポイントに追加されるため、継承を行う場合は、スーパークラスに配置します。 Product
をクラスに展開してから、Term
プロパティを指定するだけでよいと考え始めています。しかし、新しいProduct
クラスに対して同じ問題が発生します。Term
がゼータ統計にのみ適用されるのと同じように、Breakeven
はWoffet製品にのみ適用されます。
この振る舞いに遭遇すると、通常は継承が答えではない(または私が集めたのか?)場合がよくあります。それで、私はこれをモデル化するより良い方法を探しています。 Zetaを除くすべてのサブクラスを削除し、スーパークラスにコンストラクターでValueCalculationStrategyおよびVelocityCalculationStrategyを与えることを考えていました(これらはValueおよびVelocityを計算します)。しかし、この場合、私はまだゼータをStatisticPointBaseと呼んでおり、クライアントはそれがZetaStatisticPointであることを知りません。だから私はまだ行き詰っています。多分問題は私が統計とポイントを概念的に混ぜていることですか?別のZetaStatisticクラスとZetaBreakevenクラスを作成し、これらを使用してZetaPointクラスを作成することもできますが、これが正しいことかどうかはわかりません。
うまくいけば、私はこれを適切に説明しました。ご不明な点がございましたら、コメント欄でお寄せください。詳しくご説明いたします。
説明された方法で戦略オブジェクトを使用することは、PointBase
クラスを単純化するための良い出発点です。BreakEven
プロパティの問題を解決しなくても、遠慮なく紹介してください。
カスタムプロパティを許可するために、PointBase
クラスに何らかの「拡張メカニズム」を提供することができます(再設計後、名前をPoint
に変更します)。最も単純な形式では、これは各Point
オブジェクトのカスタムプロパティのリストとそれらの値のリストです(デフォルトではリストは空です)。これはある種の EAVモデル に要約されるかもしれません。 EAVはアンチパターンと見なされることもありますが、この特定のケースでは、「特別なカスタムプロパティ」に対してのみ、大部分のプロパティに使用しない限り、その使用法は問題ありません。
カスタムフィールドに関する「プログラマー」に関する以前の質問 も参照してください。私の回答で、私は言及しました Martin Fowlerの本「分析パターン」 -(カスタム)プロパティと同様のもの。
私は投稿しました ここでの質問 同じ問題によって引き起こされました:両方のサブクラスが追加のプロパティを使用すると同時に、基本クラスでオブジェクトを参照する便利さを両方利用しようとしています含む。
私にとって最も便利な解決策は、 訪問者パターン を使用することでした。 Point
基本クラスで、抽象メソッドvoid Accept(Visitor)
を定義します。 Visitor
クラスは各サブクラスのメソッドを定義します(そのため、すべてを追跡する必要があるため、新しいサブクラスではビジタークラスを変更する必要があります)。たとえば、訪問者は次のようになります。
interface Visitor {
void on1Point(1Point);
void on2Point(2Point);
... etc
}
次に、各BasePoint
サブクラスはaccept
メソッドを実装して、正しいVisitor
メソッドに委任します。したがって、1Point
クラス、accept
は次のように定義されます。
void accept(Visitor visitor) {
visitor.on1Point(this);
}
BasePoint
があり、それが持つ追加のプロパティに応じて何かを実行したい場合は常に、ビジターを作成し、可能な各サブクラスの場合に何をしたいかを理解し、そこにロジック。 Visitorメソッドの本体では、渡されるオブジェクトは正しい型に「キャスト」されるため、追加のプロパティにアクセスできます。一連のinstanceof
チェックを実行するのと似ていますが、よりクリーンで、再利用性が高く、エレガントです。
私はあなたの要件を完全には理解していませんが、私には、C#の型システムが実際に役立つよりも邪魔になるところに到達したように思えます。
私は単にクラスと継承を使用するという考えを捨て、key
がプロパティの名前またはタイプであり、value
がobject
を含む一般的なKey-Valueストアを作成するだけですプロパティの値。次に、実行時にどのタイプのポイントにどのプロパティが含まれるかについての不変条件が正しいことを確認するコードを作成します。 C#dynamic
タイプを実際に使用することもできます。これは基本的に同じことですが、構文が優れています。