Вот мое решение и проекты:
- Книжный магазин (решение)
- BookStore.Coupler (проект)
- Bootstrapper.cs
- BookStore.Domain (проект)
- CreateBookCommandValidator.cs
- CompositeValidator.cs
- IValidate.cs
- IValidator.cs
- ICommandHandler.cs
- BookStore.Infrastructure (проект)
- CreateBookCommandHandler.cs
- ValidationCommandHandlerDecorator.cs
- BookStore.Web (проект)
- Global.asax
- BookStore.BatchProcesses (проект)
- Program.cs
- BookStore.Coupler (проект)
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
Извините за долгую установку на проблему, у меня нет лучшего способа объяснить это, кроме подробного описания моей настоящей проблемы.
Я не хочу делать мой CreateBookCommandValidator public
. Я бы предпочел, internal
но если я сделаю это, internal
я не смогу зарегистрировать его с моим DI-контейнером. Причина, по которой я хотел бы, чтобы он был внутренним, заключается в том, что единственный проект, который должен иметь представление о моих реализациях IValidate <>, находится в проекте BookStore.Domain. Любой другой проект просто должен использовать IValidator <>, и CompositeValidator должен быть решен, который будет выполнять все проверки.
Как я могу использовать Dependency Injection, не нарушая инкапсуляцию? Или я все об этом говорю не так?
источник
Ответы:
Публикация
CreateBookCommandValidator
не нарушает инкапсуляцию, так какВаш
CreateBookCommandValidator
не разрешает доступ к своим членам данных (в настоящее время он, кажется, не имеет), так что это не нарушает инкапсуляцию.Публикация этого класса не нарушает никаких других принципов (таких как принципы SOLID ), потому что:
Вы можете только сделать
CreateBookCommandValidator
внутреннее случае, если класс не используется напрямую из-за пределов библиотеки, но это вряд ли когда-либо так, поскольку ваши модульные тесты являются важным потребителем этого класса (и почти каждого класса в вашей системе).Хотя вы можете сделать класс внутренним и использовать [InternalsVisibleTo], чтобы позволить проекту модульного теста получить доступ к внутренним объектам вашего проекта, зачем беспокоиться?
Самая важная причина сделать классы внутренними - запретить внешним сторонам (которые вы не контролируете) взять зависимость от такого класса, потому что это помешает вам в будущем перейти на этот класс, не нарушая ничего. Другими словами, это имеет место только при создании многократно используемой библиотеки (такой как библиотека внедрения зависимостей). На самом деле, Simple Injector содержит внутреннее содержимое, и его проект модульного тестирования тестирует эти внутренние компоненты.
Однако, если вы не создаете повторно используемый проект, эта проблема не существует. Он не существует, потому что вы можете изменить проекты, которые зависят от него, и другие разработчики в вашей команде должны будут следовать вашим рекомендациям. И подойдет одно простое руководство: запрограммируйте абстракцию; не реализация (принцип инверсии зависимости).
Короче говоря, не делайте этот класс внутренним, если вы не пишете библиотеку многократного использования.
Но если вы все еще хотите сделать этот класс внутренним, вы все равно можете зарегистрировать его в Simple Injector без каких-либо проблем, например:
Единственное, в чем нужно убедиться, это то, что у всех ваших валидаторов есть открытый конструктор, хотя они и являются внутренними. Если вы действительно хотите, чтобы у ваших типов был внутренний конструктор (на самом деле не знаю, зачем вам это нужно), вы можете переопределить поведение разрешения конструктора .
ОБНОВИТЬ
Начиная с Simple Injector v2.6 , поведение по умолчанию
RegisterManyForOpenGeneric
заключается в регистрации как открытых, так и внутренних типов. Таким образом, поставкаAccessibilityOption.AllTypes
теперь избыточна, и следующий оператор будет регистрировать как открытый, так и внутренний типы:источник
Это не имеет большого значения, что
CreateBookCommandValidator
класс является публичным.Если вам нужно создать его экземпляры за пределами библиотеки, которая его определяет, вполне естественный подход - показать открытый класс и рассчитывать на клиентов, использующих только этот класс в качестве реализации
IValidate<CreateBookCommand>
. (Простое раскрытие типа не означает, что инкапсуляция нарушена, просто клиентам будет проще нарушить инкапсуляцию).В противном случае, если вы действительно хотите заставить клиентов не знать о классе, вы можете также использовать публичный статический фабричный метод вместо предоставления класса, например:
Что касается регистрации в вашем DI-контейнере, то все DI-контейнеры, о которых я знаю, допускают создание с использованием статического фабричного метода.
источник
IValidate<>
реализации есть зависимости, поместите эти зависимости как параметры в фабричный метод.Вы можете объявить
CreateBookCommandValidator
вinternal
качестве применения атрибут InternalsVisibleToAttribute , чтобы сделать его видимым дляBookStore.Coupler
сборки. Это также часто помогает при выполнении юнит-тестов.источник
Вы можете сделать его внутренним и использовать InternalVisibleToAttribute msdn.link, чтобы ваш тестовый фреймворк / проект мог получить к нему доступ.
У меня была связанная проблема -> ссылка .
Вот ссылка на другой вопрос Stackoverflow относительно проблемы:
И, наконец, статья в Интернете.
источник
Другой вариант - сделать его публичным, но поместить его в другую сборку.
Таким образом, по сути, у вас есть сборка интерфейсов служб, сборка реализаций служб (которая ссылается на интерфейсы служб), сборка потребителей услуг (которая ссылается на интерфейсы служб) и сборка регистратора IOC (которая ссылается как на интерфейсы служб, так и на реализации служб для их соединения). ).
Я должен подчеркнуть, что это не всегда самое подходящее решение, но его стоит рассмотреть.
источник