マルチタイプのオブジェクトを設計する最良の方法
-
08-07-2019 - |
質問
データオブジェクトがあるとしますが、このオブジェクトはいくつかのタイプのデータの1つを保持できます。
class Foo
{
int intFoo;
double doubleFoo;
string stringFoo;
}
次に、アクセサーを作成します。このデータを取得する方法。明らかに、複数のアクセサーを作成できます:
public int GetIntFoo();
public double GetDoubleFoo();
public string GetStringFoo();
または、複数のプロパティを作成できます
public int IntFoo { get; set; }
public double DoubleFoo { get; set; }
public string StringFoo { get; set; }
これは非常に良いデザインではありません。それは、クライアントコードが必要以上にタイプを心配することを要求します。さらに、このクラスには実際に単一の値のみが必要であり、上記では各タイプの1つを同時に割り当てることができます。良くない。
1つのオプションは、ジェネリックを使用することです。
class Foo<T>
{
public T TheFoo { get; set; }
}
ただし、これはFooを作成するのではなく、Foo <!> lt; T <!> gt;を作成します。それぞれ異なるタイプなので、同じタイプとして実際に使用することはできません。
Foo <!> lt; T <!> gt;を導出できました。 FooBaseから、それらすべてをFooBaseとして扱いますが、データにアクセスする問題に戻ります。
別のジェネリックオプションは、次のようなものを使用することです:
class Foo
{
string stringRepresentationOfFoo;
public T GetFoo<T>() { return /* code to convert string to type */ }
}
もちろん、問題はどんな種類のTでも渡すことができるということであり、率直に言って、それは少し忙しいです。
値をボックス化してオブジェクトを返すこともできますが、タイプセーフはありません。
理想的には、すべてのFooを同じように扱いたいのですが、StringFooがない場合はStringFooへの参照をコンパイルすることさえできないように、タイプセーフが必要です。
Foo foo = new Foo("Foo");
string sFoo = foo.Value; // succeeds.
Foo foo = new Foo(0);
int iFoo = foo.Value; // succeeds
string sFoo = foo.Value; // compile error
これはおそらく不可能です。そして、私はいくつかの妥協をしなければなりませんが、多分私は何かを見逃しています。
アイデアはありますか
編集:
OK、ダニエルが指摘するように、ランタイム型のコンパイル時のチェックは実用的ではありません。
ここでやりたいことをするための最良の選択肢は何ですか?すなわち、すべてのFooを同じように扱いますが、それでも比較的健全なアクセスメカニズムを持っていますか?
EDIT2:
値を異なる型に変換したくありません。値の正しい型を返したい。つまり、それがdoubleの場合、intを返したくありません。
解決
getのパラメーターとして変数を渡すのはどうですか?このように:
int i = foo.get(i);
その後、クラスでは次のようになります:
public int get(int p) {
if(this.type != INTEGER) throw new RuntimeException("Data type mismatch");
return this.intVal;
}
public float get(float p) {
if(this.type != FLOAT) throw new RuntimeException("Data type mismatch");
return this.floatVal;
}
この種のタイプチェックは裏返しになります:fooがどのタイプを保持するかをチェックする代わりに、fooが必要なタイプをチェックします。そのタイプを提供できる場合は提供し、そうでない場合はランタイム例外をスローします。
他のヒント
これでうまくいくとは思わない(必要なコンパイラエラーが表示される)
これで何をしたい:
Foo bar = (new Random()).Next(2) == 0 ? new Foo("bar") : new Foo(1);
int baz = bar.Value;
それはコンパイラエラーですか?
<!> quot;すべて同じように扱う<!> quot; (少なくとも説明したとおり)および<!> quot; compile time error <!> quot;相互に排他的になります。
とにかく、<!> quot;最善の方法<!> quot;ジェネリックと継承の間の妥協点になります。 FooのサブクラスであるFoo<T>
を定義できます。引き続きFoo
のコレクションを持つことができます。
abstract public class Foo
{
// Common implementation
abstract public object ObjectValue { get; }
}
public class Foo<T> : Foo
{
public Foo(T initialValue)
{
Value = initialValue;
}
public T Value { get; set; }
public object ObjectValue
{
get { return Value; }
}
}
.netフレームワークのベースオブジェクトにToString()メソッドがあるように、多くのシステムはヘルパーメソッドを使用して代替タイプを返します
各オブジェクトに最適な基本型を選択し、他の場合にToメソッドを提供します
e.g。
class Foo{
public Int32 Value { get; set; }
public Byte ToByte() { return Convert.ToByte(Value); }
public Double ToDouble() { return (Double)Value; }
public new String ToString() { return Value.ToString("#,###"); }
}
1つは、クラスの内部状態に任意の型を格納することであり、もう1つは、外部に公開することです。クラスを記述するとき、実際にはその動作のコントラクトを宣言しています。記述方法は、クラスを使用するときのクライアントコードの外観に大きく影響します。
たとえば、 IConvertible インターフェースを実装することにより、型を同等の値として任意のCLR型に変換できると述べています。
また、Valueクラスを使用して、文字列、double、int、booleanのいずれかを表す計算結果を保存する実装を見てきました。しかし、問題は、クライアントコードが列挙型{String、Integer、Double、Boolean}のValue.Typeプロパティを確認し、Value.Valueプロパティ(Valueクラスによってオブジェクト型として外部に公開された)をキャストする必要があることでした)または特定のValueString、ValueDouble、ValueInt、ValueBooleanゲッターを使用します。
なぜstring
、double
およびint
を使用しないのですか?
コレクションに関する情報の後:object
の使用についてはどうですか?いずれにせよ、後でタイプなどを確認する必要があります。それに役立つように、is
およびas
演算子を使用できます。また、 Enumerable.Castメソッド、またはさらに優れた Enumerable.OfTypeメソッド。
実際、このクラスの目的は何ですか?最大の問題は、少なくともSRP(単一責任原則)を破る設計のようです。
それにもかかわらず、私がそれを正しく読んでいる場合、コンテナに値を保存し、コンテナをクライアントに渡し、値をタイプセーフに取得したいです。
このアプローチでは、提案を使用できます。つまり、
namespace Project1 {
public class Class1 {
static int Main(string[] args) {
Foo a = new Foo();
a.SetValue(4);
Console.WriteLine(a.GetValue<int>());
Foo b = new Foo();
a.SetValue("String");
Console.WriteLine(a.GetValue<string>());
Console.ReadLine();
return 0;
}
}
class Foo {
private object value; // watch out for boxing here!
public void SetValue(object value) {
this.value = value;
}
public T GetValue<T>() {
object val = this.value;
if (val == null) { return default(T); } // or throw if you prefer
try {
return (T)val;
}
catch (Exception) {
return default(T);
// cast failed, return default(T) or throw
}
}
}
}
ただし、その場合、データをオブジェクトとして渡し、自分でキャストしないのはなぜですか?
ニーズに応じて、<!> quot; PHP in C#<!> quot;:
を試すこともできます。namespace Project1 {
public class Class1 {
static int Main(string[] args) {
MyInt a = 1;
MyInt b = "2";
Console.WriteLine(a + b); // writes 3
Console.ReadLine();
return 0;
}
}
class MyInt {
private int value;
public static implicit operator int(MyInt container) {
return container.value;
}
public static implicit operator MyInt(int value) {
MyInt myInt = new MyInt();
myInt.value = value;
return myInt ;
}
public static implicit operator MyInt(string stringedInt) {
MyInt myInt = new MyInt();
myInt.value = int.Parse(stringedInt);
return myInt;
}
}
}
申し訳ありませんが、あなたの施設を購入していません。データの目的がすべて同じである場合、それらはすべて同じタイプである必要があります。いくつかのWebサービスの1つによって返される、現在の温度を保持するためのクラスを考えてください。すべてのサービスは摂氏で温度を返します。ただし、intとして返され、doubleとして返され、文字列として返されます。
3つの異なるタイプではなく、1つのタイプ-ダブルです。単純に、double以外の戻り値をdoubleに変換する必要があります。これが温度です(またはフロート)。
一般に、1つのものの複数の表現がある場合、それはまだ1つのものであり、複数のものではありません。複数の表現を1つに変換します。