Aquí está mi solución y proyectos:
- Librería (solución)
- BookStore.Coupler (proyecto)
- Bootstrapper.cs
- BookStore.Domain (proyecto)
- CreateBookCommandValidator.cs
- CompositeValidator.cs
- IValidate.cs
- IValidator.cs
- ICommandHandler.cs
- BookStore.Infrastructure (proyecto)
- CreateBookCommandHandler.cs
- ValidationCommandHandlerDecorator.cs
- BookStore.Web (proyecto)
- Global.asax
- BookStore.BatchProcesses (proyecto)
- Program.cs
- BookStore.Coupler (proyecto)
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
Perdón por la larga configuración del problema, no tengo mejor manera de explicar esto que detallar mi problema real.
No quiero hacer mi CreateBookCommandValidator public
. Preferiría que fuera, internal
pero si lo internal
hago, no podré registrarlo en mi contenedor DI. La razón por la que me gustaría que sea interna es porque el único proyecto que debería tener noción de mis implementaciones IValidate <> está en el proyecto BookStore.Domain. Cualquier otro proyecto solo necesita consumir IValidator <> y el CompositeValidator debe resolverse para cumplir con todas las validaciones.
¿Cómo puedo usar la inyección de dependencia sin romper la encapsulación? ¿O voy sobre esto todo mal?
fuente
Respuestas:
Hacer
CreateBookCommandValidator
público no viola la encapsulación, ya queSu
CreateBookCommandValidator
no permite el acceso a sus miembros de datos (actualmente no parece tener ninguno), por lo que no está violando la encapsulación.Hacer pública esta clase no viola ningún otro principio (como los principios SÓLIDOS ) porque:
Solo puede hacer el
CreateBookCommandValidator
interno si la clase no se consume directamente desde fuera de la biblioteca, pero este casi nunca es el caso, ya que sus pruebas unitarias son un consumidor importante de esta clase (y casi todas las clases en su sistema).Aunque puede hacer que la clase sea interna y usar [InternalsVisibleTo] para permitir que el proyecto de prueba de la unidad acceda a las partes internas de su proyecto, ¿por qué molestarse?
La razón más importante para hacer que las clases sean internas es evitar que las partes externas (sobre las que no tienes control) dependan de esa clase, porque eso evitaría que hagas cambios futuros en esa clase sin romper nada. En otras palabras, esto solo se cumple cuando está creando una biblioteca reutilizable (como una biblioteca de inyección de dependencia). De hecho, Simple Injector contiene material interno y su proyecto de prueba de unidad prueba estos elementos internos.
Sin embargo, si no está creando un proyecto reutilizable, este problema no existe. No existe, porque puede cambiar los proyectos que dependen de él, y los otros desarrolladores de su equipo deberán seguir sus pautas. Y una simple guía servirá: programar para una abstracción; no es una implementación (el principio de inversión de dependencia).
En resumen, no haga esta clase interna a menos que esté escribiendo una biblioteca reutilizable.
Pero si todavía desea que esta clase sea interna, puede registrarla con Simple Injector sin ningún problema como este:
Lo único de lo que debe asegurarse es que todos sus validadores tengan un constructor público, aunque sean internos. Si realmente desea que sus tipos tengan un constructor interno (no sé realmente por qué querría eso), puede anular el Comportamiento de resolución del constructor .
ACTUALIZAR
Desde Simple Injector v2.6 , el comportamiento predeterminado de
RegisterManyForOpenGeneric
es registrar los tipos públicos e internos. Por lo tanto, el suministroAccessibilityOption.AllTypes
ahora es redundante y la siguiente declaración registrará los tipos públicos e internos:fuente
No es gran cosa que la
CreateBookCommandValidator
clase sea pública.Si necesita crear instancias fuera de la biblioteca que lo define, es un enfoque bastante natural exponer la clase pública y contar con que los clientes solo usen esa clase como implementación de
IValidate<CreateBookCommand>
. (Simplemente exponer un tipo no significa que la encapsulación esté rota, solo hace que sea un poco más fácil para los clientes romper la encapsulación).De lo contrario, si realmente quiere obligar a los clientes a no saber acerca de la clase, también puede usar un método público de fábrica estática en lugar de exponer la clase, por ejemplo:
En cuanto a registrarse en su contenedor DI, todos los contenedores DI que conozco permiten la construcción utilizando un método de fábrica estático.
fuente
IValidate<>
implementación tiene dependencias, coloque estas dependencias como parámetros del método de fábrica.Puede declarar
CreateBookCommandValidator
comointernal
una aplicación InternalsVisibleToAttribute para que sea visible para elBookStore.Coupler
ensamblado. Esto a menudo también ayuda al hacer pruebas unitarias.fuente
Puede hacerlo interno y usar InternalVisibleToAttribute msdn.link para que su marco de prueba / proyecto pueda acceder a él.
Tuve un problema relacionado -> enlace .
Aquí hay un enlace a otra pregunta de Stackoverflow sobre el problema:
Y finalmente un artículo en la web.
fuente
Otra opción es hacerlo público pero ponerlo en otra asamblea.
Esencialmente, tiene un ensamblado de interfaces de servicio, un ensamblaje de implementaciones de servicio (que hace referencia a interfaces de servicio), un ensamblaje de consumidor de servicios (que hace referencia a interfaces de servicio) y un ensamblaje de registrador IOC (que hace referencia tanto a interfaces de servicio como a implementaciones de servicio para unirlos )
Debo enfatizar que esta no siempre es la solución más apropiada, pero vale la pena considerarla.
fuente