Kapsüllemeyi bozmadan Bağımlılık Enjeksiyonunu kullanabilir miyim?


15

İşte benim Çözümüm ve projelerim:

  • BookStore (çözüm)
    • BookStore.Coupler (proje)
      • Bootstrapper.cs
    • BookStore.Domain (proje)
      • CreateBookCommandValidator.cs
      • CompositeValidator.cs
      • IValidate.cs
      • IValidator.cs
      • ICommandHandler.cs
    • BookStore.Altyapı (proje)
      • CreateBookCommandHandler.cs
      • ValidationCommandHandlerDecorator.cs
    • BookStore.Web (proje)
      • Global.asax
    • BookStore.BatchProcesses (proje)
      • program.cs

Bootstrapper.cs :

public static class Bootstrapper.cs 
{
    // I'm using SimpleInjector as my DI Container
    public static void Initialize(Container container) 
    {
        container.RegisterManyForOpenGeneric(typeof(ICommandHandler<>), typeof(CreateBookCommandHandler).Assembly);
        container.RegisterDecorator(typeof(ICommandHandler<>), typeof(ValidationCommandHandlerDecorator<>));
        container.RegisterManyForOpenGeneric(typeof(IValidate<>),
            AccessibilityOption.PublicTypesOnly,
            (serviceType, implTypes) => container.RegisterAll(serviceType, implTypes),
            typeof(IValidate<>).Assembly);
        container.RegisterSingleOpenGeneric(typeof(IValidator<>), typeof(CompositeValidator<>));
    }
}

CreateBookCommandValidator.cs

public class CreateBookCommandValidator : IValidate<CreateBookCommand>
{
    public IEnumerable<IValidationResult> Validate(CreateBookCommand book)
    {
        if (book.Author == "Evan")
        {
            yield return new ValidationResult<CreateBookCommand>("Evan cannot be the Author!", p => p.Author);
        }
        if (book.Price < 0)
        {
            yield return new ValidationResult<CreateBookCommand>("The price can not be less than zero", p => p.Price);
        }
    }
}

CompositeValidator.cs

public class CompositeValidator<T> : IValidator<T>
{
    private readonly IEnumerable<IValidate<T>> validators;

    public CompositeValidator(IEnumerable<IValidate<T>> validators)
    {
        this.validators = validators;
    }

    public IEnumerable<IValidationResult> Validate(T instance)
    {
        var allResults = new List<IValidationResult>();

        foreach (var validator in this.validators)
        {
            var results = validator.Validate(instance);
            allResults.AddRange(results);
        }
        return allResults;
    }
}

IValidate.cs

public interface IValidate<T>
{
    IEnumerable<IValidationResult> Validate(T instance);
}

IValidator.cs

public interface IValidator<T>
{
    IEnumerable<IValidationResult> Validate(T instance);
}

ICommandHandler.cs

public interface ICommandHandler<TCommand>
{
    void Handle(TCommand command);
}

CreateBookCommandHandler.cs

public class CreateBookCommandHandler : ICommandHandler<CreateBookCommand>
{
    private readonly IBookStore _bookStore;

    public CreateBookCommandHandler(IBookStore bookStore)
    {
        _bookStore = bookStore;
    }

    public void Handle(CreateBookCommand command)
    {
        var book = new Book { Author = command.Author, Name = command.Name, Price = command.Price };
        _bookStore.SaveBook(book);
    }
}

ValidationCommandHandlerDecorator.cs

public class ValidationCommandHandlerDecorator<TCommand> : ICommandHandler<TCommand>
{
    private readonly ICommandHandler<TCommand> decorated;
    private readonly IValidator<TCommand> validator;

    public ValidationCommandHandlerDecorator(ICommandHandler<TCommand> decorated, IValidator<TCommand> validator)
    {
        this.decorated = decorated;
        this.validator = validator;
    }

    public void Handle(TCommand command)
    {
        var results = validator.Validate(command);

        if (!results.IsValid())
        {
            throw new ValidationException(results);
        }

        decorated.Handle(command);
    }
}

Global.asax

// inside App_Start()
var container = new Container();
Bootstrapper.Initialize(container);
// more MVC specific bootstrapping to the container. Like wiring up controllers, filters, etc..

program.cs

// Pretty much the same as the Global.asax

Sorunun uzun kurulumu için özür dilerim, bunu açıklamamın asıl sorunumu detaylandırmaktan daha iyi bir yolu yok.

CreateBookCommandValidator'ımı yapmak istemiyorum public. Olmasını tercih ederim internalama eğer yaparsam internalDI Konteynerime kayıt yapamayacağım. Dahili olmasını istememin nedeni, IValidate <> uygulamalarım hakkında düşünmesi gereken tek projenin BookStore.Domain projesinde olmasıdır. Başka herhangi bir projenin sadece IValidator <> tüketmesi gerekir ve tüm doğrulamaları yerine getirecek olan CompositeValidator çözülmelidir.

Kapsüllemeyi bozmadan Bağımlılık Enjeksiyonunu nasıl kullanabilirim? Yoksa bunların hepsini yanlış mı yapıyorum?


Sadece bir nitpick: Kullandığınız şey doğru bir komut düzeni değil, bu yüzden komut çağırmak yanlış bilgi olabilir. Ayrıca, CreateBookCommandHandler LSP'yi bozuyor gibi görünüyor: Nesneyi iletirseniz, CreateBookCommand'dan türeyen ne olacak? Ve burada yaptığınız şeyin aslında Anemik Alan Modeli antipattern olduğunu düşünüyorum. Kaydetme gibi şeyler alan adında olmalı ve doğrulama kuruluşun bir parçası olmalıdır.
Euphoric

1
@Euphoric: Doğru. Bu komut deseni değil . Nitekim OP farklı bir model izler: komut / işleyici modeli .
Steven

Ben daha çok cevap olarak işaretlenmiş olsaydı çok iyi cevaplar vardı. Yardımlarınız için herkese teşekkürler.
Evan Larsen

@Euthoric, proje düzenini yeniden düşündükten sonra, CommandHandlers'ın Etki Alanı'nda olması gerektiğini düşünüyorum. Bunları neden Altyapı projesine koyduğumdan emin değilim. Teşekkürler.
Evan Larsen

Yanıtlar:


11

Yapımı CreateBookCommandValidatorberi, kamu kapsülleme ihlal etmez

Kapsülleme, bir sınıf içindeki yapılandırılmış bir veri nesnesinin değerlerini veya durumunu gizlemek için kullanılır ve yetkisiz tarafların bunlara doğrudan erişimini önler ( wikipedia )

Sizin CreateBookCommandValidatorveri üyelerine erişime izin vermez onun ihlal değil kapsülleme böylece (o anda herhangi sahip görünmüyor).

Bu sınıfı herkese açık hale getirmek başka hiçbir ilkeyi ( SOLID ilkeleri gibi) ihlal etmez , çünkü:

  • Bu sınıfın iyi tanımlanmış tek bir sorumluluğu vardır ve bu nedenle Tek Sorumluluk Prensibi izler.
  • Sisteme yeni doğrulayıcılar eklemek tek bir kod satırını değiştirmeden yapılabilir ve bu nedenle Açık / Kapalı Prensibi'ne uyursunuz.
  • Bu sınıfın uyguladığı IValidator <T> arabirimi dardır (yalnızca bir üyesi vardır) ve Arabirim Ayırma İlkesini izler.
  • Tüketicileriniz sadece bu IValidator <T> arayüzüne bağımlıdır ve bu nedenle Bağımlılık Tersine Çevirme İlkesine uymaktadır.

Sadece yapabilirsiniz CreateBookCommandValidatorsınıf kütüphane dışında doğrudan tüketilen değilse iç ancak birim testleri bu sınıfın (ve sisteminizde hemen her sınıfın) önemli bir tüketici olduğundan bu neredeyse hiç, böyledir.

Sınıfı dahili hale getirebilir ve ünite test projesinin projenizin iç öğelerine erişmesine izin vermek için [InternalsVisibleTo] öğesini kullanabilirsiniz, ancak neden rahatsız oluyorsunuz?

Sınıfları dahili hale getirmenin en önemli nedeni, dış tarafların (üzerinde kontrol sahibi olmadığınız) böyle bir sınıfa bağımlı olmalarını önlemektir, çünkü bu, herhangi bir şeyi bozmadan gelecekte bu sınıfa geçmenizi engeller. Başka bir deyişle, bu yalnızca yeniden kullanılabilir bir kitaplık (bağımlılık enjeksiyon kitaplığı gibi) oluştururken geçerlidir. Nitekim, Basit Enjektör iç malzemeleri içerir ve birim test projesi bu içleri test eder.

Ancak, yeniden kullanılabilir bir proje oluşturmuyorsanız, bu sorun yoktur. Bu mevcut değildir, çünkü ona bağlı projeleri değiştirebilirsiniz ve ekibinizdeki diğer geliştiriciler kurallarınıza uymak zorunda kalacaktır. Ve basit bir kılavuz şunları yapacak: Bir soyutlamaya program yapın; bir uygulama değil (Bağımlılık Tersine Çevirme İlkesi).

Uzun lafın kısası, yeniden kullanılabilir bir kitaplık yazmadığınız sürece bu sınıfı dahili yapmayın.

Ancak bu sınıfı hala dahili yapmak istiyorsanız, yine de böyle bir sorun olmadan Simple Injector'a kayıt yapabilirsiniz:

container.RegisterManyForOpenGeneric(typeof(IValidate<>),
    AccessibilityOption.AllTypes,
    container.RegisterAll,
    typeof(IValidate<>).Assembly);

Emin olmanız gereken tek şey, tüm doğrulayıcılarınızın dahili olsalar bile bir kamu kurucuya sahip olmalarıdır. Türlerinizin dahili bir kurucuya sahip olmasını gerçekten istiyorsanız (neden istediğinizi gerçekten bilmiyorum) Yapıcı Çözünürlük Davranışını geçersiz kılabilirsiniz .

GÜNCELLEME

Yana Basit Enjektör v2.6 , varsayılan davranış RegisterManyForOpenGenerichalkı ve iç türlerini hem kayıt etmektir. Dolayısıyla tedarik AccessibilityOption.AllTypesartık gereksizdir ve aşağıdaki ifade hem genel hem de dahili türleri kaydedecektir:

container.RegisterManyForOpenGeneric(typeof(IValidate<>),
    container.RegisterAll,
    typeof(IValidate<>).Assembly);

8

CreateBookCommandValidatorSınıfın halka açık olması çok önemli değil .

Kütüphanenin dışında onu tanımlayan örnekler oluşturmanız gerekiyorsa, genel sınıfı ortaya çıkarmak ve bu sınıfı yalnızca bir uygulaması olarak kullanan istemcilere güvenmek oldukça doğal bir yaklaşımdır IValidate<CreateBookCommand>. (Yalnızca bir türün gösterilmesi, kapsüllemenin bozuk olduğu anlamına gelmez, sadece müşterilerin kapsüllemeyi kırmasını biraz kolaylaştırır).

Aksi takdirde, istemcileri sınıf hakkında bilmemeye gerçekten zorlamak istiyorsanız, sınıfı göstermek yerine genel statik fabrika yöntemini de kullanabilirsiniz, örneğin:

public static class Validators
{
    public static IValidate<CreateBookCommand> NewCreateBookCommandValidator()
    {
        return new CreateBookCommnadValidator();
    }
}

DI konteynerinize kayıt için, tanıdığım tüm DI konteynırları statik bir fabrika yöntemi kullanarak yapıya izin verir.


Evet, teşekkür ederim .. Bu gönderiyi oluşturmadan önce aynı şeyi düşünüyordum. Uygun IValidate <> uygulamasını geri döndürecek bir Factory sınıfı yapmayı düşünüyordum, ancak IValidate <> uygulamalarından herhangi birinin herhangi bir bağımlılığı varsa, muhtemelen hızlı bir şekilde kıllı olur.
Evan Larsen

@EvanLarsen Neden? IValidate<>Uygulamanın bağımlılıkları varsa, bu bağımlılıkları fabrika yöntemine parametre olarak ekleyin.
jhominal



1

Başka bir seçenek, onu herkese açık hale getirmek, ancak başka bir meclise koymaktır.

Bu nedenle, temel olarak, bir hizmet arabirimleri derlemesine, bir hizmet uygulamaları derlemesine (hizmet arabirimlerine gönderme yapan), bir hizmet tüketici derlemesine (hizmet arabirimlerine gönderme yapan) ve bir IOC kayıt kuruluşu derlemesine (bunları birleştirmek için hem hizmet arabirimlerine hem de hizmet uygulamalarına başvuruda bulunur) ).

Vurgulamalıyım, bu her zaman en uygun çözüm değil, ama göz önünde bulundurmaya değer.


Bu, iç kısımları görünür kılma güvenlik riskini ortadan kaldırır mı?
Johannes

1
@Johannes: Güvenlik riski var mı? Size güvenlik sağlamak için erişim değiştiricilere güveniyorsanız endişelenmeniz gerekir. Yansıma yoluyla herhangi bir yönteme erişebilirsiniz. Ancak, uygulamayı başvuruda bulunulmayan başka bir derleme içine koyarak iç / kolay erişimi teşvik eder.
pdr
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.