Oto moje rozwiązanie i projekty:
- BookStore (rozwiązanie)
- BookStore.Coupler (projekt)
- Bootstrapper.cs
- BookStore.Domain (projekt)
- CreateBookCommandValidator.cs
- CompositeValidator.cs
- IValidate.cs
- IValidator.cs
- ICommandHandler.cs
- BookStore.Infrastructure (projekt)
- CreateBookCommandHandler.cs
- ValidationCommandHandlerDecorator.cs
- BookStore.Web (projekt)
- Global.asax
- BookStore.BatchProcesses (projekt)
- Program.cs
- BookStore.Coupler (projekt)
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
Przepraszam za długą konfigurację problemu, nie mam lepszego sposobu na wyjaśnienie tego niż opisanie mojego rzeczywistego problemu.
Nie chcę tworzyć mojego CreateBookCommandValidator public
. Wolałbym, internal
ale jeśli to internal
zrobię, nie będę mógł zarejestrować go w moim kontenerze DI. Powodem, dla którego chciałbym, aby był wewnętrzny, jest to, że jedynym projektem, który powinien mieć pojęcie o moich implementacjach IValidate <>, jest projekt BookStore.Domain. Każdy inny projekt musi po prostu zużyć IValidator <>, a CompositeValidator powinien zostać rozwiązany, co spełni wszystkie kryteria sprawdzania poprawności.
Jak korzystać z Dependency Injection bez przerywania enkapsulacji? A może źle to robię?
źródło
Odpowiedzi:
Upublicznienie
CreateBookCommandValidator
nie narusza enkapsulacji, ponieważTwój
CreateBookCommandValidator
nie pozwala na dostęp do danych swoich członków (obecnie wydaje się nie mieć w ogóle), więc jej nie łamie hermetyzacji.Upublicznienie tej klasy nie narusza żadnej innej zasady (takiej jak zasady SOLID ), ponieważ:
Możesz zrobić
CreateBookCommandValidator
wewnętrzny tylko wtedy, gdy klasa nie jest zużywana bezpośrednio spoza biblioteki, ale rzadko tak jest, ponieważ testy jednostkowe są ważnym konsumentem tej klasy (i prawie każdej klasy w twoim systemie).Chociaż możesz ustawić klasę jako wewnętrzną i użyć [InternalsVisibleTo], aby umożliwić projektowi testu jednostkowego dostęp do wewnętrznych elementów projektu, po co zawracać sobie tym głowę?
Najważniejszym powodem, aby uczynić klasy wewnętrznymi, jest uniemożliwienie podmiotom zewnętrznym (nad którymi nie masz kontroli) uzależnienia się od takich klas, ponieważ uniemożliwiłoby to w przyszłości przejście do tej klasy bez zerwania czegokolwiek. Innymi słowy, dotyczy to tylko tworzenia biblioteki wielokrotnego użytku (takiej jak biblioteka wstrzykiwania zależności). W rzeczywistości Simple Injector zawiera elementy wewnętrzne, a projekt ich testów jednostkowych testuje te elementy wewnętrzne.
Jeśli jednak nie tworzysz projektu wielokrotnego użytku, ten problem nie istnieje. Nie istnieje, ponieważ możesz zmieniać projekty od niego zależne, a pozostali programiści w twoim zespole będą musieli przestrzegać twoich wskazówek. I zrobi to jedna prosta wytyczna: Zaprogramuj abstrakcję; nie implementacja (zasada odwrócenia zależności).
Krótko mówiąc, nie wprowadzaj tej klasy wewnętrznej, chyba że piszesz bibliotekę wielokrotnego użytku.
Ale jeśli nadal chcesz uczynić tę klasę wewnętrzną, możesz zarejestrować ją w Simple Injector bez żadnego problemu:
Jedyną rzeczą do upewnienia się jest to, że wszystkie wasze walidatory mają publicznego konstruktora, nawet jeśli są wewnętrzne. Jeśli naprawdę chcesz, aby twoje typy miały wewnętrzny konstruktor (nie wiem, dlaczego tak chcesz), możesz przesłonić zachowanie rozdzielczości konstruktora .
AKTUALIZACJA
Od Simple Injector v2.6 domyślnym zachowaniem
RegisterManyForOpenGeneric
jest rejestracja typów publicznych i wewnętrznych. DostarczanieAccessibilityOption.AllTypes
jest teraz zbędne, a następujące oświadczenie zarejestruje zarówno typy publiczne, jak i wewnętrzne:źródło
Nie jest wielkim problemem, że
CreateBookCommandValidator
klasa jest publiczna.Jeśli musisz utworzyć jego instancje poza biblioteką, która ją definiuje, jest to całkiem naturalne podejście do ujawnienia klasy publicznej i liczenie na klientów używających tej klasy tylko jako implementacji
IValidate<CreateBookCommand>
. (Samo ujawnienie typu nie oznacza, że enkapsulacja jest zepsuta, po prostu ułatwia klientom jej przerwanie).W przeciwnym razie, jeśli naprawdę chcesz zmusić klientów, aby nie wiedzieli o klasie, możesz także użyć publicznej statycznej metody fabrycznej zamiast ujawniać klasę, np .:
Jeśli chodzi o rejestrację w kontenerze DI, wszystkie kontenery DI, które znam, pozwalają na budowę przy użyciu statycznej metody fabrycznej.
źródło
IValidate<>
implementacja ma zależności, umieść te zależności jako parametry w metodzie fabrycznej.Możesz zadeklarować
CreateBookCommandValidator
jakointernal
zastosowanie InternalsVisibleToAttribute , aby był widoczny dlaBookStore.Coupler
zespołu. Często pomaga to również podczas przeprowadzania testów jednostkowych.źródło
Możesz ustawić go jako wewnętrzny i użyć pliku msdn.link InternalVisibleToAttribute, aby struktura / projekt testowy mógł uzyskać do niego dostęp.
Miałem powiązany problem -> link .
Oto link do innego pytania Stackoverflow dotyczącego problemu:
I wreszcie artykuł w Internecie.
źródło
Inną opcją jest upublicznienie go, ale umieszczenie go w innym zestawie.
Zasadniczo masz zestaw interfejsów usług, zestaw implementacji usług (który odwołuje się do interfejsów usług), zestaw konsumenta usług (który odwołuje się do interfejsów usług) oraz zestaw rejestratora IOC (który odwołuje się zarówno do interfejsów usług, jak i implementacji usług, aby je połączyć ).
Chciałbym podkreślić, że nie zawsze jest to najbardziej odpowiednie rozwiązanie, ale warto je rozważyć.
źródło