2015-01-06 7 views
6

更新:Reposted on Programmers Stack Exchange主にオピニオンベースとして保留されているためです。構造体を使用して組み込み型の検証を強制するための賛否両論

一般的に、ドメインオブジェクトは、組み込み型で表現できますが、有効な値はその型で表される値のサブセットです。

このような場合、組み込み型を使用して値を格納できますが、値が入力時点で常に検証されるようにする必要があります。そうしないと、無効な値で処理される可能性があります。

これを解決する1つの方法は、structというカスタムとして値を格納することです。これは、組み込み型の1つのprivate readonlyバッキングフィールドを持ち、そのコンストラクタが指定された値を検証します。このstructタイプを使用して、常に検証された値のみを使用することができます。

また、キャスト演算子を基になる組み込み型との間で提供することによって、値が基になる型としてシームレスに入力および終了できるようにすることもできます。

例として、ドメインオブジェクトの名前を表す必要があり、有効な値は1〜255文字の長さの任意の文字列です。

public struct ValidatedName : IEquatable<ValidatedName> 
{ 
    private readonly string _value; 

    private ValidatedName(string name) 
    { 
     _value = name; 
    } 

    public static bool IsValid(string name) 
    { 
     return !String.IsNullOrEmpty(name) && name.Length <= 255; 
    } 

    public bool Equals(ValidatedName other) 
    { 
     return _value == other._value; 
    } 

    public override bool Equals(object obj) 
    { 
     if (obj is ValidatedName) 
     { 
      return Equals((ValidatedName)obj); 
     } 
     return false; 
    } 

    public static implicit operator string(ValidatedName x) 
    { 
     return x.ToString(); 
    } 

    public static explicit operator ValidatedName(string x) 
    { 
     if (IsValid(x)) 
     { 
      return new ValidatedName(x); 
     } 
     throw new InvalidCastException(); 
    } 

    public static bool operator ==(ValidatedName x, ValidatedName y) 
    { 
     return x.Equals(y); 
    } 

    public static bool operator !=(ValidatedName x, ValidatedName y) 
    { 
     return !x.Equals(y); 
    } 

    public override int GetHashCode() 
    { 
     return _value.GetHashCode(); 
    } 

    public override string ToString() 
    { 
     return _value; 
    } 
} 

の例では、このようimplicitとしてツーstringキャストが失敗することはできません示していますが、このようexplicitとしてfrom- stringキャストが無効な値のためにスローされますが、もちろんの:私たちは、次の構造体を使用して、これを表すことができこれらはいずれもimplicitまたはexplicitのいずれかです。

stringからのキャストによってのみこの構造体を初期化することができますが、IsValidstaticメソッドを使用してそのようなキャストが先に失敗するかどうかをテストできます。

これは、単純なタイプで表現できるドメイン値の検証を強制するのに適しているようですが、頻繁に使用されたり、示唆されたりしません。理由について興味があります。

私の質問は、このパターンを使用することのメリットとデメリットは何ですか?

このパターンが悪いと思うなら、私は理由を理解したいと思っています。また、あなたが感じるものが最良の選択肢です。

+0

私のバリデータには、特定の名前空間のファクトリクラスまたは静的クラスのいずれかを介してアクセスできるドメインロジックです。つまり、すべての検証を含む 'ClientValidator'のような名前空間内の' MyApp.Validators'とクラス固有のバリデータです。私は実際に構造体の使用を見ていない – Franck

+0

プログラマに移行する候補者はおそらく、確かにただの意見ではない –

+1

私は私の答えを提供するだろうが、Q&Aを得るためにremaning 1近い投票で閉鎖.. .... –

答えて

0

あなたのやり方はかなり複雑ですが、あなたの意図が検証されたときに等価性テストとキャスティングを追加する必要がありました。

あなたが

...あなたがNULLまたは空白も、その後、句読点やValdatedNameType3と長さが8つの& 16文字の間ですることはできませんvalidatedname2が必要な場合である鉱山は、例えば、属性を使用することでしょう

allowed_blankと最大長またはおそらく正規表現

+0

それは良い考えです。彼はすべてのプロパティに属性を追加し、任意のクラスを取り、リフレクションを使用してすべてのバリデータ属性を取得し、それらを1つずつプロパティに適用するメソッドを作成することができます。非常に汎用的で拡張可能です。 – Franck

+0

私は、このような型が多い場合、これらをすべて作成する手助けになる可能性があることに同意しますが、多くの型では、テンプレートを使用してこれを実現できます。組み込み型を使用して表現されます。 – gmoody1979

+0

属性を使用する際に表示される問題:まず、ドメインの値の種類が複数の場所で使用されている場合、これらの属性をすべての用途に追加する必要があります。次に重要なのは、属性ベースの検証を使用している状況にあることです。これはおそらく、ドメインオブジェクトの検証がどこで処理されるべきかというより根本的な問題になると思います。しかし、それははるかに基本的で、より広く、主観的な質問であることが分かります。 – gmoody1979

0

あなたの方法はかなり重くて集中的です。私は、通常のようなドメインエンティティ定義:エンティティのコンストラクタで

public class Institution 
{ 
    private Institution() { } 

    public Institution(int organizationId, string name) 
    { 
     OrganizationId = organizationId;    
     Name = name; 
     ReplicationKey = Guid.NewGuid(); 

     new InstitutionValidator().ValidateAndThrow(this); 
    } 

    public int Id { get; private set; } 
    public string Name { get; private set; }   
    public virtual ICollection<Department> Departments { get; private set; } 

    ... other properties  

    public Department AddDepartment(string name) 
    { 
     var department = new Department(Id, name); 
     if (Departments == null) Departments = new List<Department>(); 
     Departments.Add(department);    
     return department; 
    } 

    ... other domain operations 
} 

を、検証はFluentValidationを使用してトリガされます。NETを使用して、無効な状態のエンティティを作成できないようにします。プロパティはすべて読み取り専用であることに注意してください。コンストラクタまたは専用ドメイン操作を通じてのみプロパティを設定できます。

このエンティティの検証は別のクラスである:

public class InstitutionValidator : AbstractValidator<Institution> 
{ 
    public InstitutionValidator() 
    { 
     RuleFor(institution => institution.Name).NotNull().Length(1, 100).WithLocalizedName(() => Prim.Mgp.Infrastructure.Resources.GlobalResources.InstitutionName);  
     RuleFor(institution => institution.OrganizationId).GreaterThan(0); 
     RuleFor(institution => institution.ReplicationKey).NotNull().NotEqual(Guid.Empty); 
    } 
} 

これらのバリデータも簡単に再利用することができ、あなたがより少ない定型的なコードを記述します。

+0

これははるかに一般的であるように見えるパターンのタイプです。しかし、これは、例えば機関名の検証が機関の財産としてのその名前の使用に結びついているという欠点を抱えていませんか?体系化されていない機関名をInstitutionオブジェクトのコンテキストから使いたい場合はどうすればよいでしょうか? – gmoody1979

+0

その場合、より一般的なInstitutionNameValidatorを作成し、さまざまなエンティティで使用できます。これらのバリデーターは再利用可能なので、あなたは望みどおりにすることができます... –

+0

はい、より直接的に同等のパターンですが、値が検証された時点と終了していない時点を常に把握しておく必要があります。また、同じことを繰り返し検証したり、まったく。また、シナリオに応じて問題の可能性があるかどうかを検証したいとき、または少なくとも手元にあるときにValidatorオブジェクトを作成するオーバーヘッドがあります。 – gmoody1979

関連する問題