どのように設計変更不能なオブジェクトとの複合体の初期化
-
21-08-2019 - |
質問
私は学習DDDしての計算書は、"価値オブジェクト"は不変です。理解しているということになオブジェクトの状態は変化しないで作成されます。このような新しい考え方が私にとってもう多い。
ですが、私の作成を開始不変の価値オブジェクト。
- いずれの状態全体のパラメータとしてのコンストラクタ
- んが、追加性セッター、
- とがないことを確認の方法はプログラムの使用が許可されコンテンツ(返却のみの新しいインスタンス).
ものを作りたいこの値をオブジェクトを含む8種類の数値です。場を作りたいのコンストラクタを持8数値パラメータ感じませんので非常に使いやすい、いや-れすることを目的とするものである、間違いのないようにいたします。このできないデザイン。
その質問質問質問質問質問: その他の作り方を私の不変のオブジェクトより..、マジックできるクライアントまで、フルのC#の克服に長いパラメータのリストのコンストラクタ?語聴覚デ..
更新: 前にも言及し、ひとつのアイディアを議論においてはこちら:変更不能なオブジェクトのパターンクライアントまで、フルのC#-いかがですか?
の関係について聞き取り調査その他のご提案やご意見ものです。
解決
ビルダーを使用します:
public class Entity
{
public class Builder
{
private int _field1;
private int _field2;
private int _field3;
public Builder WithField1(int value) { _field1 = value; return this; }
public Builder WithField2(int value) { _field2 = value; return this; }
public Builder WithField3(int value) { _field3 = value; return this; }
public Entity Build() { return new Entity(_field1, _field2, _field3); }
}
private int _field1;
private int _field2;
private int _field3;
private Entity(int field1, int field2, int field3)
{
// Set the fields.
}
public int Field1 { get { return _field1; } }
public int Field2 { get { return _field2; } }
public int Field3 { get { return _field3; } }
public static Builder Build() { return new Builder(); }
}
次にようにそれを作成します
Entity myEntity = Entity.Build()
.WithField1(123)
.WithField2(456)
.WithField3(789)
.Build()
いくつかのパラメータがオプションである場合は、あなたはWithXXXメソッドを呼び出す必要はありません、彼らは、デフォルト値を持つことができます。
他のヒント
現在、い用のコンストラクタの引数は、ビルダを構築します。クライアントまで、フルのC#4.0(VS2010)を使用でき名/オプション引数に何かを達成することにな同様にC#3.0オブジェクトinitializers見 こちらの.の例をブログには:
Person p = new Person ( forename: "Fred", surname: "Flintstone" );
設定が簡単にでどのようなもので任意のコンストラクタ(またはその他の複雑な法)。そして、C#3.0オブジェクト初期化子書式(可変型):
Person p = new Person { Forename = "Fred", Surname = "Flintstone" };
いを教えてくれるかですね。
Jon Skeetはあいにくの思いがこも こちらの.
私の頭の上から、二つの異なる答えが気になる...
...最初の、そしておそらく最も簡単な、あなたは物事を得る確実にヘルパーとしてオブジェクトファクトリ(またはビルダー)を使用することです右ます。
オブジェクトの初期化は次のようになります:
var factory = new ObjectFactory();
factory.Fimble = 32;
factory.Flummix = "Nearly";
var mine = factory.CreateInstance();
... 2番目は、ロック()またはフリーズ()関数で、従来、可変、オブジェクトとして、あなたのオブジェクトを作成することです。あなたのミューテーターのすべてのオブジェクトがロックされているかどうかを確認し、それが持っている場合に例外をスローする必要があります。
オブジェクトの初期化は次のようになります:
var mine = new myImmutableObject();
mine.Fimble = 32;
mine.Flummix = "Nearly";
mine.Lock(); // Now it's immutable.
工場出荷時は、あなたが構築するための類似した一連のオブジェクトを持っている場合に便利であるという利点を持っていますが、それは書いて、維持するために別のクラスを紹介しません - 取るための方法は、あなたの状況に大きく依存します。ロック可能なオブジェクトがある唯一のクラスであるが、他のユーザーが予期しない実行時エラーを取得する可能性がありますし、テストが困難であることを意味します。
それはおそらくあなたが何をしているかのドメインの一部であり、したがって、私の提案は無効である可能性がありますが、どのような論理グループに8つのパラメータを打破しようとするでしょうか?
私はパラメータの山を見るたびに、、私は、オブジェクト/メソッド/コンストラクタは、単純であるべきように感じます。
私は複雑なコンストラクタと同じ質問でboggledされているにも私に悪いデザインです。維持するためにあまりにも多くの余分なコードのように思えるように私はまた、ビルダーの概念の大ファンではありません。必要なのは、あなたがプロパティのセッターを使用することが許可されている場合、オブジェクトが変更可能なようにして起動することを意味し、アイスキャンデー不変です。すべてのプロパティが設定されると不変状態にオブジェクトを凍結する方法がなければなりません。この戦略は、残念ながら、C#言語でネイティブにサポートされていません。従って私はこの質問で説明したように不変オブジェクトを作成するために自分のパターンを設計することになった。
C#でアンダース・ヘルスバーグは、次のインタビューで36:30から不変のこのタイプのためのサポートについて話されます:
の未来あなたが一緒にsetメソッド/関数連鎖するために、(モナド機能的なスタイルを使用して)メソッドのような「セッター」を作るためにオブジェクトと怠惰のすべてのフィールドを初期化するためにリフレクションを使用することができます。
例
あなたは、この基本クラスを使用することができます:
public class ImmutableObject<T>
{
private readonly Func<IEnumerable<KeyValuePair<string, object>>> initContainer;
protected ImmutableObject() {}
protected ImmutableObject(IEnumerable<KeyValuePair<string,object>> properties)
{
var fields = GetType().GetFields().Where(f=> f.IsPublic);
var fieldsAndValues =
from fieldInfo in fields
join keyValuePair in properties on fieldInfo.Name.ToLower() equals keyValuePair.Key.ToLower()
select new {fieldInfo, keyValuePair.Value};
fieldsAndValues.ToList().ForEach(fv=> fv.fieldInfo.SetValue(this,fv.Value));
}
protected ImmutableObject(Func<IEnumerable<KeyValuePair<string,object>>> init)
{
initContainer = init;
}
protected T setProperty(string propertyName, object propertyValue, bool lazy = true)
{
Func<IEnumerable<KeyValuePair<string, object>>> mergeFunc = delegate
{
var propertyDict = initContainer == null ? ObjectToDictonary () : initContainer();
return propertyDict.Select(p => p.Key == propertyName? new KeyValuePair<string, object>(propertyName, propertyValue) : p).ToList();
};
var containerConstructor = typeof(T).GetConstructors()
.First( ce => ce.GetParameters().Count() == 1 && ce.GetParameters()[0].ParameterType.Name == "Func`1");
return (T) (lazy ? containerConstructor.Invoke(new[] {mergeFunc}) : DictonaryToObject<T>(mergeFunc()));
}
private IEnumerable<KeyValuePair<string,object>> ObjectToDictonary()
{
var fields = GetType().GetFields().Where(f=> f.IsPublic);
return fields.Select(f=> new KeyValuePair<string,object>(f.Name, f.GetValue(this))).ToList();
}
private static object DictonaryToObject<T>(IEnumerable<KeyValuePair<string,object>> objectProperties)
{
var mainConstructor = typeof (T).GetConstructors()
.First(c => c.GetParameters().Count()== 1 && c.GetParameters().Any(p => p.ParameterType.Name == "IEnumerable`1") );
return mainConstructor.Invoke(new[]{objectProperties});
}
public T ToObject()
{
var properties = initContainer == null ? ObjectToDictonary() : initContainer();
return (T) DictonaryToObject<T>(properties);
}
}
そのように実装することができます:
public class State:ImmutableObject<State>
{
public State(){}
public State(IEnumerable<KeyValuePair<string,object>> properties):base(properties) {}
public State(Func<IEnumerable<KeyValuePair<string, object>>> func):base(func) {}
public readonly int SomeInt;
public State someInt(int someInt)
{
return setProperty("SomeInt", someInt);
}
public readonly string SomeString;
public State someString(string someString)
{
return setProperty("SomeString", someString);
}
}
このように使用することができます:
//creating new empty object
var state = new State();
// Set fields, will return an empty object with the "chained methods".
var s2 = state.someInt(3).someString("a string");
// Resolves all the "chained methods" and initialize the object setting all the fields by reflection.
var s3 = s2.ToObject();