Inilah Solusi dan proyek saya:
- BookStore (solusi)
- BookStore.Coupler (proyek)
- Bootstrapper.cs
- BookStore.Domain (proyek)
- BuatBookCommandValidator.cs
- CompositeValidator.cs
- IValidate.cs
- IValidator.cs
- ICommandHandler.cs
- BookStore.Infrastructure (proyek)
- BuatBookCommandHandler.cs
- ValidationCommandHandlerDecorator.cs
- BookStore.Web (proyek)
- Global.asax
- BookStore.BatchProcesses (proyek)
- Program.cs
- BookStore.Coupler (proyek)
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<>));
}
}
BuatBookCommandValidator.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);
}
BuatBookCommandHandler.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
Maaf untuk pengaturan panjang untuk masalah ini, saya tidak memiliki cara yang lebih baik untuk menjelaskan hal ini selain merinci masalah saya yang sebenarnya.
Saya tidak ingin membuat CreateBookCommandValidator saya public
. Saya lebih suka internal
tetapi jika saya membuatnya internal
maka saya tidak akan bisa mendaftarkannya dengan Container DI saya. Alasan saya ingin ini bersifat internal adalah karena satu-satunya proyek yang seharusnya memiliki gagasan tentang implementasi <> IValidate saya adalah dalam proyek BookStore.Domain. Setiap proyek lain hanya perlu mengkonsumsi IValidator <> dan CompositeValidator harus diselesaikan yang akan memenuhi semua validasi.
Bagaimana saya bisa menggunakan Injeksi Ketergantungan tanpa melanggar enkapsulasi? Atau apakah saya salah tentang semua ini?
sumber
Jawaban:
Membuat
CreateBookCommandValidator
publik tidak melanggar enkapsulasi, sejakAnda
CreateBookCommandValidator
tidak mengizinkan akses ke anggota datanya (saat ini sepertinya tidak ada) sehingga tidak melanggar enkapsulasi.Membuat kelas ini menjadi publik tidak melanggar prinsip lain apa pun (seperti prinsip SOLID ), karena:
Anda hanya dapat membuat
CreateBookCommandValidator
internal jika kelas tidak dikonsumsi langsung dari luar perpustakaan, tetapi ini jarang terjadi, karena unit test Anda adalah konsumen penting kelas ini (dan hampir setiap kelas di sistem Anda).Meskipun Anda dapat membuat kelas internal dan menggunakan [InternalsVisibleTo] untuk memungkinkan proyek uji unit mengakses internal proyek Anda, mengapa repot-repot?
Alasan paling penting untuk membuat kelas internal adalah untuk mencegah pihak eksternal (bahwa Anda tidak memiliki kendali atas) untuk mengambil ketergantungan pada kelas seperti itu, karena itu akan mencegah Anda membuat perubahan di masa depan ke kelas itu tanpa merusak apa pun. Dengan kata lain, ini hanya berlaku ketika Anda membuat perpustakaan yang dapat digunakan kembali (seperti perpustakaan injeksi ketergantungan). Faktanya, Simple Injector berisi hal-hal internal dan proyek uji unitnya menguji internal ini.
Namun, jika Anda tidak membuat proyek yang dapat digunakan kembali, masalah ini tidak ada. Tidak ada, karena Anda dapat mengubah proyek yang bergantung padanya, dan pengembang lain di tim Anda harus mengikuti panduan Anda. Dan satu pedoman sederhana akan dilakukan: Program ke abstraksi; bukan implementasi (Prinsip Pembalikan Ketergantungan).
Singkat cerita, jangan buat kelas ini internal kecuali Anda menulis perpustakaan yang dapat digunakan kembali.
Tetapi jika Anda masih ingin menjadikan kelas ini internal, Anda masih dapat mendaftarkannya dengan Simple Injector tanpa masalah seperti ini:
Satu-satunya hal yang perlu dipastikan adalah bahwa semua validator Anda memiliki konstruktor publik, meskipun mereka internal. Jika Anda benar-benar ingin tipe Anda memiliki konstruktor internal (tidak benar-benar tahu mengapa Anda menginginkannya), Anda dapat mengesampingkan Perilaku Resolusi Pembuat .
MEMPERBARUI
Sejak Simple Injector v2.6 , perilaku default
RegisterManyForOpenGeneric
adalah untuk mendaftarkan tipe publik dan internal. Jadi penyediaanAccessibilityOption.AllTypes
sekarang berlebihan dan pernyataan berikut akan mendaftarkan tipe publik dan internal:sumber
Bukan masalah besar bahwa
CreateBookCommandValidator
kelas itu publik.Jika Anda perlu membuat instance di luar perpustakaan yang mendefinisikannya, itu adalah pendekatan yang cukup alami untuk mengekspos kelas publik, dan mengandalkan klien yang hanya menggunakan kelas itu sebagai implementasi dari
IValidate<CreateBookCommand>
. (Mengekspos suatu tipe tidak berarti enkapsulasi rusak, itu hanya membuat klien lebih mudah untuk memecah enkapsulasi).Kalau tidak, jika Anda benar-benar ingin memaksa klien untuk tidak tahu tentang kelas, Anda juga dapat menggunakan metode pabrik statis publik alih-alih mengekspos kelas, misalnya:
Adapun mendaftar di wadah DI Anda, semua wadah DI yang saya tahu memungkinkan konstruksi dengan menggunakan metode pabrik statis.
sumber
IValidate<>
implementasi memiliki dependensi, maka taruh dependensi ini sebagai parameter ke metode pabrik.Anda dapat mendeklarasikan
CreateBookCommandValidator
sebagaiinternal
memberlakukan InternalsVisibleToAttribute untuk membuatnya terlihat olehBookStore.Coupler
majelis. Ini juga sering membantu ketika melakukan tes unit.sumber
Anda dapat membuatnya internal dan menggunakan msdn.link InternalVisibleToAttribute sehingga kerangka kerja / proyek pengujian Anda dapat mengaksesnya.
Saya memiliki masalah terkait -> tautan .
Berikut ini tautan ke pertanyaan Stackoverflow lain mengenai masalah ini:
Dan akhirnya sebuah artikel di web.
sumber
Pilihan lain adalah mempublikasikannya tetapi menempatkannya di majelis lain.
Jadi pada dasarnya, Anda memiliki unit antarmuka layanan, unit implementasi layanan (yang mereferensikan antarmuka layanan), unit layanan konsumen (yang mereferensikan antarmuka layanan), dan unit pendaftar IOC (yang mereferensikan antarmuka layanan dan implementasi layanan untuk menghubungkan keduanya. ).
Saya harus menekankan, ini tidak selalu merupakan solusi yang paling tepat, tetapi ini patut dipertimbangkan.
sumber