Aqui está minha solução e projetos:
- BookStore (solução)
- BookStore.Coupler (projeto)
- Bootstrapper.cs
- BookStore.Domain (projeto)
- CreateBookCommandValidator.cs
- CompositeValidator.cs
- IValidate.cs
- IValidator.cs
- ICommandHandler.cs
- BookStore.Infrastructure (projeto)
- CreateBookCommandHandler.cs
- ValidationCommandHandlerDecorator.cs
- BookStore.Web (projeto)
- Global.asax
- BookStore.BatchProcesses (projeto)
- Program.cs
- BookStore.Coupler (projeto)
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
Desculpe pela longa configuração do problema, não tenho outra maneira de explicar isso além de detalhar meu problema real.
Não quero criar meu CreateBookCommandValidator public
. Eu prefiro que seja, internal
mas se eu fizer, internal
não poderei registrá-lo no meu DI Container. A razão pela qual eu gostaria que fosse interno é porque o único projeto que deveria ter noção das minhas implementações IValidate <> está no projeto BookStore.Domain. Qualquer outro projeto precisa apenas consumir IValidator <> e o CompositeValidator deve ser resolvido para cumprir todas as validações.
Como posso usar a injeção de dependência sem quebrar o encapsulamento? Ou estou fazendo tudo errado?
fonte
Respostas:
Tornar
CreateBookCommandValidator
público não viola o encapsulamento, poisSeu
CreateBookCommandValidator
não permite acesso aos membros de dados (atualmente não parece ter nenhum), portanto não está violando o encapsulamento.A divulgação desta classe não viola nenhum outro princípio (como os princípios do SOLID ), porque:
Você só pode tornar
CreateBookCommandValidator
interno se a classe não for consumida diretamente de fora da biblioteca, mas isso quase nunca acontece, pois seus testes de unidade são um consumidor importante dessa classe (e quase todas as classes em seu sistema).Embora você possa tornar a classe interna e usar [InternalsVisibleTo] para permitir que o projeto de teste da unidade acesse as internas do seu projeto, por que se preocupar?
O motivo mais importante para tornar as classes internas é impedir que partes externas (sobre as quais você não tem controle) dependam dessa classe, porque isso impediria que você fizesse alterações futuras nessa classe sem quebrar nada. Em outras palavras, isso só é válido quando você está criando uma biblioteca reutilizável (como uma biblioteca de injeção de dependência). De fato, o Simple Injector contém material interno e seu projeto de teste de unidade testa esses componentes internos.
No entanto, se você não estiver criando um projeto reutilizável, esse problema não existe. Ele não existe, porque você pode alterar os projetos que dependem dele e os outros desenvolvedores da sua equipe terão que seguir suas diretrizes. E uma diretriz simples fará: Programar para uma abstração; não é uma implementação (o Princípio da inversão de dependência).
Resumindo a história, não torne essa classe interna, a menos que você esteja escrevendo uma biblioteca reutilizável.
Mas se você ainda deseja tornar essa classe interna, ainda pode registrá-la no Simple Injector sem nenhum problema como este:
A única coisa a garantir é que todos os seus validadores tenham um construtor público, mesmo que sejam internos. Se você realmente deseja que seus tipos tenham um construtor interno (realmente não sei por que você desejaria isso), você pode substituir o Comportamento de resolução do construtor .
ATUALIZAR
Desde o Simple Injector v2.6 , o comportamento padrão de
RegisterManyForOpenGeneric
é registrar os tipos público e interno. Portanto, o fornecimentoAccessibilityOption.AllTypes
agora é redundante e a seguinte declaração registra os tipos público e interno:fonte
Não é grande coisa que a
CreateBookCommandValidator
classe seja pública.Se você precisar criar instâncias fora da biblioteca que a define, é uma abordagem bastante natural expor a classe pública e contar com os clientes apenas usando essa classe como implementação de
IValidate<CreateBookCommand>
. (Simplesmente expor um tipo não significa que o encapsulamento está quebrado, apenas torna um pouco mais fácil para os clientes quebrarem o encapsulamento).Caso contrário, se você realmente deseja forçar os clientes a não conhecerem a classe, também pode usar um método público de fábrica estática em vez de expor a classe, por exemplo:
Quanto ao registro em seu contêiner DI, todos os contêineres DI que conheço permitem a construção usando um método estático de fábrica.
fonte
IValidate<>
implementação tiver dependências, coloque-as como parâmetros no método de fábrica.Você pode declarar
CreateBookCommandValidator
comointernal
aplicar o InternalsVisibleToAttribute para torná-lo visível para oBookStore.Coupler
assembly. Isso também costuma ajudar ao fazer testes de unidade.fonte
Você pode torná-lo interno e usar o InternalVisibleToAttribute msdn.link para que sua estrutura / projeto de teste possa acessá-lo.
Eu tive um problema relacionado -> link .
Aqui está um link para outra pergunta do Stackoverflow sobre o problema:
E, finalmente, um artigo na web.
fonte
Outra opção é torná-lo público, mas colocá-lo em outra montagem.
Portanto, basicamente, você tem um conjunto de interfaces de serviço, um conjunto de implementações de serviço (que faz referência a interfaces de serviço), um conjunto de consumidor de serviço (que faz referência a interfaces de serviço) e um conjunto de registrador IOC (que faz referência a interfaces de serviço e implementações de serviço para conectá-los) )
Devo enfatizar que nem sempre é a solução mais adequada, mas vale a pena considerar.
fonte