Yerleşik türün doğrulanmasını sağlamak için struct kullanma


9

Genellikle etki alanı nesneleri yerleşik bir türle temsil edilebilecek, ancak geçerli değerleri bu türle temsil edilebilecek değerlerin bir alt kümesi olan özelliklere sahiptir.

Bu durumlarda, değer yerleşik tip kullanılarak saklanabilir, ancak değerlerin her zaman giriş noktasında doğrulandığından emin olmak gerekir, aksi takdirde geçersiz bir değerle çalışabiliriz.

Bunu çözmenin bir yolu, değeri yerleşik türün structtek bir private readonlydestek alanına sahip olan ve yapıcısı sağlanan değeri doğrulayan bir özel olarak depolamaktır . Daha sonra bu structtürü kullanarak her zaman yalnızca onaylanmış değerleri kullandığımızdan emin olabiliriz .

Ayrıca, temeldeki yerleşik türden gelen ve bu türe döküm operatörleri sağlayabiliriz, böylece değerler altta yatan tür olarak sorunsuz bir şekilde girebilir ve çıkabilir.

Örnek olarak, bir etki alanı nesnesinin adını temsil etmemiz gereken bir durum ele alalım ve geçerli değerler, uzunluğu 1 ile 255 karakter arasında olan herhangi bir dizedir. Bunu aşağıdaki yapıyı kullanarak temsil edebiliriz:

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;
    }
}

Örnek gösterir to stringolarak dökme implicitolarak bu başarısız asla ama dan- stringolarak dökme explicitolarak bu geçersiz değerler için fırlatır, tabii bunlar her ikisi de ya olabilir implicitveya explicit.

Ayrıca, bu yapıyı yalnızca bir döküm yoluyla stringbaşlatabileceğini, ancak böyle bir dökümün IsValid staticyöntemi kullanarak önceden başarısız olup olmayacağını test edebileceğini unutmayın .

Bu, basit türlerle temsil edilebilen alan değerlerinin doğrulanmasını zorunlu kılmak için iyi bir model gibi görünebilir, ancak sık sık kullanıldığını veya önerildiğini görmüyorum ve nedeniyle ilgileniyorum.

Benim sorum şu: Bu modeli kullanmanın avantajları ve dezavantajları olarak neler görüyorsunuz ve neden?

Bunun kötü bir kalıp olduğunu düşünüyorsanız, neden ve ne hissettiğinizin en iyi alternatif olduğunu anlamak istiyorum.

NB Başlangıçta Stack Overflow üzerine bu soruyu sordum ama öncelikle görüş tabanlı (kendi başına ironik olarak öznel) olarak beklemeye alındı ​​- umarım burada daha fazla başarı elde edebilirsiniz.

Yukarıda orijinal metin, birkaç düşünce daha altında, kısmen orada beklemeden önce alınan cevaplara yanıt olarak:

  • Cevaplar tarafından yapılan en önemli noktalardan biri, özellikle bu tür birçok tip gerektiğinde, yukarıdaki model için gerekli olan kazan plakası kodunun miktarı idi. Ancak, kalıbın savunmasında, bu şablonlar kullanılarak büyük ölçüde otomatik hale getirilebilir ve aslında bana göre çok da kötü görünmüyor, ama bu sadece benim düşüncem.
  • Kavramsal bir bakış açısından, C # gibi güçlü yazılan bir dille çalışırken, güçlü yazılan prensibi bileşik değerlere uygulamak yerine garip görünmüyor. yerleşik tip?

bir bool (T) lambda alır templated bir sürüm yapabilir
cırcır ucube

Yanıtlar:


4

Bu, sarmalayıcı türlerini oluşturmanın çok daha kolay olduğu Standart ML / OCaml / F # / Haskell gibi ML tarzı dillerde oldukça yaygındır. Size iki avantaj sağlar:

  • Bir kod parçasının, bir dizenin, bu doğrulamanın kendisine bakmak zorunda kalmadan, doğrulamadan geçtiğini zorlamasına izin verir.
  • Doğrulama kodunu tek bir yerde yerelleştirmenizi sağlar. Bir Eğer ValidatedNameşimdiye geçersiz bir değer içeriyor, hata olduğunu biliyorum IsValidyöntemle.

Eğer alırsanız IsValidyöntem hakkı, herhangi bir işlevi aldığı bir garanti varValidatedName aslında doğrulanmış ad alma.

Dize manipülasyonları yapmanız gerekiyorsa, bir String (değeri ValidatedName) alan ve bir String (yeni değer) döndüren ve işlevi uygulama sonucunu doğrulayan bir işlevi kabul eden genel bir yöntem ekleyebilirsiniz . Bu, temel Dize değerini almanın ve yeniden sarmanın kaynak plakasını ortadan kaldırır.

Değerlerin kaydırılmasıyla ilgili bir kullanım, bunların provenanslarını takip etmektir. Örneğin, C tabanlı işletim sistemi API'ları bazen kaynaklar için tamsayı olarak işlemektedir. Bunun yerine bir Handleyapı kullanmak ve yalnızca kodun o kısmına yapıcıya erişim sağlamak için OS API'lerini kapatabilirsiniz . S'yi üreten kod Handledoğruysa, yalnızca geçerli tanıtıcılar kullanılır.


1

bu modeli kullanmanın avantajları ve dezavantajları olarak neler görüyorsunuz ve neden?

İyi :

  • Kendi kendine yetmektedir. Çok fazla sayıda doğrulama bitinin farklı yerlere ulaşan dalları vardır.
  • Kendi kendine belgelemeye yardımcı olur. Bir yöntem görmek ValidatedStringçağrının anlambilimi hakkında daha açık hale getirir.
  • Genel yöntemlerde çoğaltılması gerekmeden doğrulamanın bir nokta ile sınırlandırılmasına yardımcı olur.

Kötü :

  • Döküm hilesi gizlidir. Deyimsel C # değil, bu yüzden kodu okurken karışıklığa neden olabilir.
  • Atar. Doğrulamayı karşılamayan dizelere sahip olmak istisnai bir senaryo değildir. IsValidOyunculardan önce yapmak biraz tuhaf.
  • Bir şeyin neden geçersiz olduğunu söyleyemez.
  • Varsayılan değer ValidatedStringgeçersiz / geçerli değil.

Bunu daha sık birlikte gidecek şeyi gördüm Userve AuthenticatedUsernesne aslında değişen şeylerin tür. C # 'da yerinde görünmese de iyi bir yaklaşım olabilir.


1
Teşekkürler, dördüncü "con" buna karşı en zorlayıcı argüman olduğunu düşünüyorum - varsayılan veya tür bir dizi kullanarak (sıfır / null dizesinin geçerli bir değer olup olmadığına bağlı olarak) geçersiz değerler verebilir. Bunlar (bence) geçersiz bir değer elde etmenin tek iki yolu. Ama sonra, eğer bu kalıbı KULLANAMAZsak, bu iki şey yine de bize geçersiz değerler verirdi, ama en azından bunların onaylanması gerektiğini biliyoruz. Dolayısıyla bu, altta yatan türün varsayılan değerinin türümüz için geçerli olmadığı yaklaşımı geçersiz kılabilir.
gmoody1979

Tüm eksileri, kavram ile ilgili sorunlar yerine uygulama sorunları vardır. Ayrıca "istisnalar istisnai olmalı" bulanık ve kötü tanımlanmış bir kavram buluyorum. En pragmatik yaklaşım, hem istisna tabanlı hem de istisnasız tabanlı bir yöntem sağlamak ve arayanın seçim yapmasına izin vermektir.
Doval

@Doval benim diğer yorumda belirtilen dışında katılıyorum. Desenin tüm amacı, bir ValidatedName'ımız varsa, geçerli olması gerektiğinden emin olmaktır. Bu, temel alınan türün varsayılan değeri de etki alanı türünün geçerli bir değeri değilse bozulur. Bu elbette etki alanına bağımlıdır, ancak dize tabanlı türler için sayısal türlere göre daha olasıdır (düşünürdüm). Desen, temel alınan türün varsayılan değerinin etki alanı türünün varsayılan değeri olarak uygun olduğu yerlerde en iyi şekilde çalışır.
gmoody1979

@Doval - Genel olarak katılıyorum. Kavramın kendisi iyidir, ancak etkili bir şekilde ayakkabı çeken arıtma türlerini desteklemeyen bir dile dönüştürmeye çalışır. Her zaman uygulama sorunları olacak.
Telastyn

Söyledikten sonra, "giden" döküm ve herhangi bir başka gerekli yerde yapı yöntemleri içinde varsayılan değeri kontrol ve başlatılmazsa atmak varsayalım, ama bu dağınık olmaya başlar varsayalım.
gmoody1979

0

Yolunuz oldukça ağır ve yoğun. Genellikle aşağıdaki gibi etki alanı varlıklarını tanımlar:

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
}

Varlığın yapıcısında, geçersiz duruma sahip bir varlık oluşturamayacağınızdan emin olmak için FluentValidation.NET kullanılarak doğrulama tetiklenir. Özelliklerin salt okunur olduğunu unutmayın - bunları yalnızca yapıcı veya özel alan adı işlemleri aracılığıyla ayarlayabilirsiniz.

Bu varlığın doğrulanması ayrı bir sınıftır:

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);
    }  
}

Bu doğrulayıcılar da kolayca yeniden kullanılabilir ve daha az kazan plakası kodu yazarsınız. Ve bir başka avantajı da okunabilir olmasıdır.


Düşürücü, cevabımın neden düşürüldüğünü açıklamak ister mi?
L-Four

Soru, değer türlerini sınırlayacak bir yapı ile ilgiliydi ve NEDEN'i açıklamadan bir sınıfa geçtiniz. (Bir düşürücü değil, sadece bir öneri yapıyor.)
DougM

Bunu neden daha iyi bir alternatif bulduğumu açıkladım ve bu onun sorularından biriydi. Yanıtınız için teşekkürler.
L-Four

0

Değer türlerine bu yaklaşımı seviyorum. Konsept harika, ama uygulama hakkında bazı öneriler / şikayetler var.

Döküm : Bu durumda döküm kullanımını sevmiyorum. Açık dize döküm bir sorun değildir, ancak (ValidatedName)nameValueyeni ve arasında çok fazla fark yoktur ValidatedName(nameValue). Yani gereksiz görünüyor. Örtülü dizge dökümü en kötü sorundur. Yanlışlıkla dize ve derleyici atanmış olabilir çünkü gerçek dize değeri almak daha açık olması gerektiğini düşünüyorum "olası hassasiyet kaybı" hakkında sizi uyarmaz. Bu tür bir hassas kayıp açık olmalıdır.

ToString : ToStringAşırı yükleri sadece hata ayıklama amacıyla kullanmayı tercih ederim . Ve bunun için ham değeri döndürmenin iyi bir fikir olduğunu düşünmüyorum. Bu, örtük dizgi dönüşümü ile aynı konudur. Dahili değerin elde edilmesi açık bir işlem olmalıdır. Yapının dış koda normal bir dize gibi davranmaya çalıştığınıza inanıyorum, ancak bunu yaparken, bu tür bir tür uygulamaktan elde ettiğiniz değerin bir kısmını kaybettiğinizi düşünüyorum.

Eşittir ve GetHashCode : Yapılar varsayılan olarak yapısal eşitliği kullanır. Yani sizin Equalsve GetHashCodebu varsayılan davranışı çoğaltırsınız. Onları kaldırabilirsiniz ve hemen hemen aynı şey olacaktır.


Döküm: Anlamsal olarak bu bana yeni bir ValidatedName oluşturmak yerine bir dizenin ValidatedName'e dönüştürülmesi gibi geliyor: Varolan bir dizeyi ValidatedName olarak tanımlıyoruz. Bu yüzden benim için oyuncular anlamsal olarak daha doğru görünüyor. Yazarken (klavye çeşitliliğindeki parmakların) çok az fark olduğu kabul edildi. Ben-string döküm üzerinde katılmıyorum: ValidatedName dize bir alt kümesidir, bu yüzden asla bir
kesinti

ToString: Kabul etmiyorum. Benim için ToString, hata ayıklama senaryolarının dışında, gereksinimi karşıladığı varsayılarak kullanmak için mükemmel geçerli bir yöntemdir. Ayrıca, bir türün başka bir türün alt kümesi olduğu bu durumda, yeteneğin alt kümeden süper kümeye dönüşümü mümkün olduğunca kolay hale getirmek mantıklıdır, böylece kullanıcı isterse, neredeyse set türü, yani dize ...
gmoody1979

Eşittir ve GetHashCode: Evet yapıları yapısal eşitliği kullanır, ancak bu örnekte dizenin değerini değil dizgi başvurusunu karşılaştırır. Bu nedenle Eşittirleri geçersiz kılmamız gerekir. Altta yatan tür bir değer türüyse bunun gerekli olmayacağını kabul ediyorum. Değer türleri için (oldukça sınırlı olan) varsayılan GetHashCode uygulamasını anladığımdan, bu aynı değeri verecektir, ancak daha fazla performans gösterecektir. Durumun bu olup olmadığını gerçekten test etmeliyim, ancak sorunun ana noktası için biraz yan sorun. Bu arada cevabınız için teşekkür ederim :-).
gmoody1979

@ gmoody1979 Yapılar varsayılan olarak her alanda Eşittir kullanılarak karşılaştırılır. Dizelerle ilgili bir sorun olmamalı. GetHashCode ile aynı. Yapının dize alt kümesi olması. Türü güvenlik ağı olarak düşünmeyi seviyorum. ValidatedName ile çalışmak ve sonra yanlışlıkla dize kullanmak için kayma istemiyorum. Derleyici beni şimdi kontrol edilmemiş verilerle çalışmak istediğimi açıkça belirtmiş olsaydı tercih ederim.
Euphoric

Üzgünüm evet, Eşittir. Geçersiz kılma daha iyi performans göstermesine rağmen, varsayılan davranışın karşılaştırmayı yapmak için yansıma kullanması gerekir. Döküm: evet, açık bir kadro haline getirmek için muhtemelen iyi bir argüman.
15:23
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.