17

さまざまな理由から、デザインでより不変なタイプを使い始めたいと思います。現在、私は次のような既存のクラスを持つプロジェクトで作業しています。

public class IssueRecord
{
    // The real class has more readable names :)
    public string Foo { get; set; }
    public string Bar { get; set; }
    public int Baz { get; set; }
    public string Prop { get; set; }
    public string Prop2 { get; set; }
    public string Prop3 { get; set; }
    public string Prop4 { get; set; }
    public string Prop5 { get; set; }
    public string Prop6 { get; set; }
    public string Prop7 { get; set; } 
    public string Prop8 { get; set; } 
    public string Prop9 { get; set; }
    public string PropA { get; set; }
}

このクラスは、実際にこれほど多くのプロパティを持っているディスク上のフォーマットを表しているため、この時点では、それをより小さなビットにリファクタリングすることはほとんど問題外です。

これは、このクラスのコンストラクターが不変の設計で実際に13個のパラメーターを持っている必要があることを意味しますか?そうでない場合、この設計を不変にする場合、コンストラクターで受け入れられるパラメーターの数を減らすためにどのような手順を実行できますか?

4

6 に答える 6

14

引数の数を減らすには、それらを適切なセットにグループ化できますが、真に不変のオブジェクトを作成するには、コンストラクター/ファクトリメソッドで初期化する必要があります。

いくつかのバリエーションは、流暢なインターフェースで構成できる「ビルダー」クラスを作成し、最終オブジェクトを要求することです。これは、コードのさまざまな場所にそのようなオブジェクトの多くを実際に作成することを計画している場合は理にかなっています。そうでない場合は、1つの場所に多くの引数を作成することは許容できるトレードオフになる可能性があります。

var immutable = new MyImmutableObjectBuilder()
  .SetProp1(1)
  .SetProp2(2)
  .Build();
于 2012-09-06T01:17:39.130 に答える
13

これは、このクラスのコンストラクターが不変の設計で実際に13個のパラメーターを持っている必要があることを意味しますか?

一般的に、はい。13個のプロパティを持つ不変型には、これらすべての値を初期化するための何らかの手段が必要になります。

それらがすべて使用されていない場合、または一部のプロパティが他のプロパティに基づいて決定できる場合は、パラメーターが少ない1つ以上のオーバーロードされたコンストラクターを使用できます。ただし、コンストラクター(型が不変であるかどうかに関係なく)は、型が論理的に「正しく」「完全」であるように、型のデータを完全に初期化する必要があります。

このクラスは、実際にこれほど多くのプロパティを持っているディスク上のフォーマットを表しているため、この時点では、それをより小さなビットにリファクタリングすることはほとんど問題外です。

「オンディスクフォーマット」が実行時に決定されるものである場合、初期化データ(つまり、ファイル名など)を取得し、完全に初期化されたタイプを構築するファクトリメソッドまたはコンストラクターを使用できる可能性があります。

于 2012-09-06T01:05:41.373 に答える
3

おそらく、現在のクラスをそのままにして、可能であれば適切なデフォルトを提供し、IssueRecordOptionsに名前を変更します。これを、不変のIssueRecordへの単一の初期化パラメーターとして使用します。

于 2012-09-06T01:03:16.967 に答える
3

コンストラクターでは、名前付き引数とオプションの引数を組み合わせて使用​​できます。値が常に異なる場合は、はい、非常識なコンストラクターで立ち往生しています。

于 2012-09-06T01:04:55.780 に答える
2

構造体を作成することはできますが、それでも構造体を宣言する必要があります。しかし、常に配列などがあります。それらがすべて同じデータ型である場合は、配列、リスト、文字列など、いくつかの方法でグループ化できます。あなたは正しいように見えますが、不変の型はすべて、何らかの方法でコンストラクターを通過する必要があり、13個のパラメーターを通過するか、構造体、配列、リストなどを通過する必要があります...

于 2012-09-06T00:58:05.570 に答える
0

コンパイル時に割り当てを禁止することが目的の場合は、コンストラクターの割り当てとプライベートセッターに固執する必要があります。ただし、これには多くの欠点があります。新しいメンバーの初期化やxmlの非セラリゼーションなどを使用することはできません。

私はこのようなものを提案します:

    public class IssuerRecord
    {
        public string PropA { get; set; }
        public IList<IssuerRecord> Subrecords { get; set; }
    }

    public class ImmutableIssuerRecord
    {
        public ImmutableIssuerRecord(IssuerRecord record)
        {
            PropA = record.PropA;
            Subrecords = record.Subrecords.Select(r => new ImmutableIssuerRecord(r));
        }

        public string PropA { get; private set; }
        // lacks Count and this[int] but it's IReadOnlyList<T> is coming in 4.5.
        public IEnumerable<ImmutableIssuerRecord> Subrecords { get; private set; }

        // you may want to get a mutable copy again at some point.
        public IssuerRecord GetMutableCopy()
        {
            var copy = new IssuerRecord
                           {
                               PropA = PropA,
                               Subrecords = new List<IssuerRecord>(Subrecords.Select(r => r.GetMutableCopy()))
                           };
            return copy;
        }
    }

ここのIssuerRecordは、はるかに説明的で便利です。他の場所に渡すと、不変バージョンを簡単に作成できます。不変で動作するコードは読み取り専用ロジックを備えている必要があるため、IssuerRecordと同じタイプであるかどうかは気にしないでください。オブジェクトをラップするのではなく、他の場所で変更される可能性があるため、各フィールドのコピーを作成しますが、特にシーケンシャル同期呼び出しでは必要ない場合があります。ただし、完全に不変のコピーを「後で」コレクションに保存する方が安全です。ただし、一部のコードで変更を禁止したいが、オブジェクトの状態の更新を受信できるようにしたい場合は、アプリケーションのラッパーになる可能性があります。

var record = new IssuerRecord { PropA = "aa" };
if(!Verify(new ImmutableIssuerRecord(record))) return false;

C ++の用語で考えると、ImmutableIssuerRecordsは「IssuerRecordconst」と見なすことができます。ただし、不変オブジェクトが所有するオブジェクトを保護するために細心の注意を払う必要があります。そのため、すべての子のコピーを作成することをお勧めします(サブレコードの例)。

ImmutableIssuerRecord.SubrecorsはここではIEnumerableであり、Countとthis []がありませんが、IReadOnlyListは4.5で提供され、必要に応じてドキュメントからコピーできます(後で簡単に移行できます)。

Freezableなどの他のアプローチもあります。

public class IssuerRecord
{
    private bool isFrozen = false;

    private string propA;
    public string PropA
    { 
        get { return propA; }
        set
        {
            if( isFrozen ) throw new NotSupportedOperationException();
            propA = value;
        }
    }

    public void Freeze() { isFrozen = true; }
}

これにより、コードが再び読みにくくなり、コンパイル時の保護が提供されなくなります。ただし、通常どおりオブジェクトを作成し、準備ができたらフリーズすることができます。

ビルダーパターンも考慮すべき点ですが、私の観点からすると、「サービス」コードが多すぎます。

于 2012-09-06T03:04:39.127 に答える