Voici ma solution et mes projets:
- Librairie (solution)
- BookStore.Coupler (projet)
- Bootstrapper.cs
- BookStore.Domain (projet)
- CreateBookCommandValidator.cs
- CompositeValidator.cs
- IValidate.cs
- IValidator.cs
- ICommandHandler.cs
- BookStore.Infrastructure (projet)
- CreateBookCommandHandler.cs
- ValidationCommandHandlerDecorator.cs
- BookStore.Web (projet)
- Global.asax
- BookStore.BatchProcesses (projet)
- Program.cs
- BookStore.Coupler (projet)
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
Désolé pour la longue configuration du problème, je n'ai pas de meilleur moyen d'expliquer cela que de détailler mon problème réel.
Je ne veux pas créer mon CreateBookCommandValidator public
. Je préférerais que ce soit, internal
mais si je le fais, internal
je ne pourrai pas l'enregistrer avec mon conteneur DI. La raison pour laquelle je voudrais que ce soit interne est parce que le seul projet qui devrait avoir la notion de mes implémentations IValidate <> est dans le projet BookStore.Domain. Tout autre projet doit simplement consommer IValidator <> et le CompositeValidator doit être résolu, ce qui remplira toutes les validations.
Comment puis-je utiliser l'injection de dépendance sans interrompre l'encapsulation? Ou est-ce que je me trompe?
la source
Réponses:
Rendre le
CreateBookCommandValidator
public ne viole pas l'encapsulation, carVotre
CreateBookCommandValidator
ne permet pas l'accès à ses membres de données (il ne semble pas en avoir actuellement), donc il ne viole pas l'encapsulation.Rendre cette classe publique ne viole aucun autre principe (comme les principes SOLID ), car:
Vous ne pouvez faire de l'
CreateBookCommandValidator
interne que si la classe n'est pas consommée directement de l'extérieur de la bibliothèque, mais ce n'est presque jamais le cas, car vos tests unitaires sont un consommateur important de cette classe (et presque toutes les classes de votre système).Bien que vous puissiez rendre la classe interne et utiliser [InternalsVisibleTo] pour permettre au projet de test unitaire d'accéder aux internes de votre projet, pourquoi s'embêter?
La raison la plus importante pour rendre les classes internes est d'empêcher des parties externes (sur lesquelles vous n'avez pas de contrôle) de prendre une dépendance sur une telle classe, car cela vous empêcherait de faire évoluer ultérieurement cette classe sans rien casser. En d'autres termes, cela ne s'applique que lorsque vous créez une bibliothèque réutilisable (telle qu'une bibliothèque d'injection de dépendances). En fait, Simple Injector contient des éléments internes et son projet de test unitaire teste ces éléments internes.
Cependant, si vous ne créez pas de projet réutilisable, ce problème n'existe pas. Il n'existe pas, car vous pouvez modifier les projets qui en dépendent et les autres développeurs de votre équipe devront suivre vos consignes. Et une simple directive suffira: Programmer vers une abstraction; pas une implémentation (le principe d'inversion de dépendance).
Donc, pour faire court, ne faites pas de cette classe interne, sauf si vous écrivez une bibliothèque réutilisable.
Mais si vous voulez toujours rendre cette classe interne, vous pouvez toujours l'enregistrer avec Simple Injector sans aucun problème comme celui-ci:
La seule chose à vous assurer est que tous vos validateurs ont un constructeur public, même s'ils sont internes. Si vous voulez vraiment que vos types aient un constructeur interne (je ne sais pas vraiment pourquoi vous le voudriez), vous pouvez remplacer le comportement de résolution du constructeur .
MISE À JOUR
Depuis Simple Injector v2.6 , le comportement par défaut de
RegisterManyForOpenGeneric
est d'enregistrer les types publics et internes. La fournitureAccessibilityOption.AllTypes
est donc désormais redondante et la déclaration suivante enregistrera les types publics et internes:la source
Ce n'est pas grave que la
CreateBookCommandValidator
classe soit publique.Si vous devez en créer des instances en dehors de la bibliothèque qui la définit, c'est une approche assez naturelle pour exposer la classe publique et compter sur les clients utilisant uniquement cette classe comme implémentation de
IValidate<CreateBookCommand>
. (Le simple fait d'exposer un type ne signifie pas que l'encapsulation est rompue, cela permet simplement aux clients de rompre l'encapsulation un peu plus facilement).Sinon, si vous voulez vraiment forcer les clients à ne pas connaître la classe, vous pouvez également utiliser une méthode d'usine statique publique au lieu d'exposer la classe, par exemple:
En ce qui concerne l'enregistrement dans votre conteneur DI, tous les conteneurs DI que je connais permettent la construction en utilisant une méthode d'usine statique.
la source
IValidate<>
implémentation a des dépendances, placez ces dépendances en tant que paramètres dans la méthode d'usine.Vous pouvez déclarer
CreateBookCommandValidator
eninternal
tant qu'application l' InternalsVisibleToAttribute afin de le rendre visible à l'BookStore.Coupler
assembly. Cela aide également souvent lors des tests unitaires.la source
Vous pouvez le rendre interne et utiliser le msdn.link InternalVisibleToAttribute afin que votre framework / projet de test puisse y accéder.
J'ai eu un problème connexe -> lien .
Voici un lien vers une autre question Stackoverflow concernant le problème:
Et enfin un article sur le web.
la source
Une autre option est de le rendre public mais de le mettre dans une autre assemblée.
Donc, essentiellement, vous avez un assemblage d'interfaces de service, un assemblage d'implémentations de service (qui fait référence aux interfaces de service), un assemblage de consommateur de service (qui fait référence aux interfaces de service) et un assemblage de registraire IOC (qui fait référence à la fois aux interfaces de service et aux implémentations de service pour les connecter ensemble) ).
Je dois souligner que ce n'est pas toujours la solution la plus appropriée, mais elle mérite d'être envisagée.
la source