14

私はDDDについて学んでいて、「値オブジェクト」は不変でなければならないというステートメントに出くわしました。これは、オブジェクトの作成後にオブジェクトの状態が変更されないことを意味することを理解しています。これは私にとっては新しい考え方ですが、多くの場合、それは理にかなっています。

さて、不変の値オブジェクトの作成を開始します。

  • 状態全体をコンストラクターへのパラメーターとして受け取るようにします。
  • プロパティセッターは追加しませんが、
  • また、コンテンツを変更するメソッドが許可されていないことを確認してください(新しいインスタンスのみを返します)。

しかし、ここで、8つの異なる数値を含むこの値オブジェクトを作成したいと思います。8つの数値パラメーターを持つコンストラクターを作成すると、使い勝手が悪くなると思います。つまり、数値を渡すときに間違いを犯しやすくなります。これは良いデザインにはなり得ません。

したがって、質問は次のとおりです。不変オブジェクトを改善する他の方法はありますか。コンストラクターの長いパラメーターリストを克服するためにC#で実行できる魔法はありますか?私はあなたの考えを聞くことに非常に興味があります。

更新:誰かがそれについて言及する前に、1つのアイデアがここで議論されました: C#の不変オブジェクトパターン-あなたはどう思いますか?

ただし、他の提案やコメントを聞くことに興味があります。

4

6 に答える 6

22

ビルダーを使用します。

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メソッドを呼び出す必要はなく、デフォルト値を使用できます。

于 2008-12-10T05:56:33.230 に答える
9

現時点では、多くの引数を持つコンストラクターまたはビルダーを使用する必要があります。C# 4.0 (VS2010) では、名前付き/オプションの引数を使用して、C# 3.0 のオブジェクト初期化子に似た機能を実現できます。こちらを参照してください。ブログの例は次のとおりです。

  Person p = new Person ( forename: "Fred", surname: "Flintstone" );

しかし、コンストラクター (またはその他の複雑なメソッド) に同様のことがどのように適用されるかは簡単にわかります。C# 3.0 オブジェクト初期化子の構文 (変更可能な型) と比較してください。

 Person p = new Person { Forename = "Fred", Surname = "Flintstone" };

本当に、それらを区別することはあまりありません。

Jon Skeet も、このテーマについていくつかの考えを投稿しています

于 2008-12-10T08:30:37.270 に答える
3

頭のてっぺんから、2つの異なる答えが思い浮かびます...

...最初の、そしておそらく最も簡単なのは、オブジェクトファクトリ(またはビルダー)をヘルパーとして使用して、物事を正しく行うことです。

オブジェクトの初期化は次のようになります。

var factory = new ObjectFactory();
factory.Fimble = 32;
factory.Flummix = "Nearly";
var mine = factory.CreateInstance();

... 2つ目は、Lock()またはFreeze()関数を使用して、オブジェクトを従来の可変オブジェクトとして作成することです。すべてのミューテーターは、オブジェクトがロックされているかどうかを確認し、ロックされている場合は例外をスローする必要があります。

オブジェクトの初期化は次のようになります。

var mine = new myImmutableObject();
mine.Fimble = 32;
mine.Flummix = "Nearly";
mine.Lock(); // Now it's immutable.

どのメソッドを使用するかは、コンテキストによって大きく異なります。ファクトリには、構築する一連の同様のオブジェクトがある場合に便利であるという利点がありますが、作成および保守する別のクラスが導入されます。ロック可能なオブジェクトは、クラスが1つしかないことを意味しますが、他のユーザーは予期しないランタイムエラーを受け取る可能性があり、テストはより困難です。

于 2008-12-10T06:12:11.420 に答える
2

それはおそらくあなたがやっていることの領域の一部であり、したがって私の提案は無効かもしれませんが、8 つのパラメーターを論理的なグループに分解しようとするのはどうですか?

パラメーターの山を見るたびに、オブジェクト/メソッド/コンストラクターはもっと単純であるべきだと感じます。

于 2008-12-10T08:40:44.253 に答える
1

複雑なコンストラクターも私にとって悪い設計であるため、私は同じ質問に悩まされてきました。また、維持するには余分なコードが多すぎるように思われるため、ビルダーの概念の大ファンでもありません。私たちが必要としているのはポプシクルの不変性です。つまり、プロパティ セッターの使用が許可されている場合、オブジェクトは可変として開始されます。すべてのプロパティが設定されている場合、オブジェクトを凍結して不変の状態にする方法が必要です。残念ながら、この戦略は C# 言語でネイティブにサポートされていません。したがって、この質問で説明されているように、不変オブジェクトを作成するための独自のパターンを設計することになりました。

C# の不変オブジェクト パターン - どう思いますか?

Anders Hejlsberg は、次のインタビューの 36:30 から、このタイプの不変性のサポートについて語っています。

専門家から専門家へ: Anders Hejlsberg - C# の未来

于 2009-05-06T06:23:40.867 に答える
1

オブジェクトのすべてのフィールドを初期化するためにリフレクションを使用し、遅延を使用して「セッター」のようなメソッドを作成し (モナディック関数スタイルを使用)、セット メソッド/関数を連鎖させることができます。

例えば:

この基本クラスを使用できます。

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();
于 2014-06-26T13:17:18.313 に答える