La source de votre problème est le fait que vous essayez de construire l'application ici composants (les implémentations de vos règles métier) avec des données d'exécution (propriétés d'une instance CreditCard
connue uniquement à l'exécution), tandis que injecting application components with runtime data is an anti-pattern. Au lieu de cela, vos composants doivent être sans état et en transmettant des données d'exécution via l'API publique de l'abstraction IRule
, vous évitez d'avoir à créer ce composant en usine (depuis factories are a code smell), et vous évitez ces problèmes de maintenance comme vous l'avez décrit. dans vos questions.
@InBetween a fait un très bon commentaire au sujet de faire l'abstraction IRule
générique, car cela permet de créer une règle de gestion de type sécurisé mise en œuvre qui définit exactement ce qu'il vérifie:
public interface IBusinessRule<TEntity>
{
IEnumerable<string> Validate(TEntity entity);
}
Notez également que j'ai changé Validate
donc qu'il ne renvoie pas un booléen, mais plutôt une collection d'erreurs de validation (zéro ou plus). Cela permet de communiquer plus clairement pourquoi le système a cessé de traiter votre demande.
Implémentations peut se présenter comme suit:
classe CreditCardNameNotEmpty: IBusinessRule { publique IEnumerable Valider (entité CreditCard) { if (chaîne.IsNullOrWhiteSpace (entity.Name) rendement return "Le nom de la carte de crédit ne doit pas être vide."; En déplaçant les données d'exécution hors du constructeur, cela nous permet maintenant de construire plus facilement des composants d'application qui contiennent leurs propres dépendances. Par exemple:
classe CreditCardDateIsValid: IBusinessRule { private readonly ILogger logger; public CreditCardDateIsValid (ILogger logger) { this.logger; }
public IEnumerable<string> Validate(CreditCard entity) {
// etc
}
}
Bien que nous pourrions injecter un IEnumerable<IBusinessRule<T>>
en composants qui ont besoin de validation de règles métier, ce ne serait pas bon de le faire, parce que cela obligerait le consommateur à itérer la collection retournée, qui causerait beaucoup de duplication de code. Donc, à la place, nous voulons cacher l'abstraction IBusinessRule<T>
du consommateur et lui présenter une abstraction plus centrée sur ses besoins. Par exemple:
public interface IValidator<T>
{
// Throws a ValidationException in case of a validation error.
void Validate(T instance);
}
Nous pouvons facilement mettre en œuvre cela comme suit:
public class Validator<T> : IValidator<T>
{
private readonly IEnumerable<IBusinessRule<T>> rules;
public Validator(IEnumerable<IBusinessRule<T>> rules) {
if (rules == null) throw new ArgumentNullException(nameof(rules));
this.rules = rules;
}
public void Validate(T instance) {
if (instance == null) throw new ArgumentNullException(nameof(instance));
var errorMessages = rules.Select(rule => rule.Validate(instance)).ToArray();
if (errorMessages.Any()) throw new ValidationException(errorMessages);
}
}
Cela nous permet de simplifier le processeur de paiement à ce qui suit:
class MyPaymentProcess : IPaymentProcessor
{
private readonly IValidator<CreditCard> creditCardValidator;
public MyPaymentProcess(IValidator<CreditCard> creditCardValidator) {
this.creditCardValidator = creditCardValidator;
}
public void MakePayment(CreditCard card)
{
this.creditCardValidator.Validate(card);
// continue the payment
}
}
Notez que la méthode MakePayment
maintenant ne retourne plus un bool
. C'est parce que si une opération ne peut pas faire ce qu'elle promet de faire (dans ce cas, faire le paiement), elle devrait lancer une exception. En retournant un booléen, vous renvoyez un code d'erreur, une pratique que nous avons laissée derrière nous il y a longtemps.
Peut-être que vous avez besoin d'une interface avec la règle de validation booléenne (carte de crédit), et pas seulement de valider(). – Evk
Oui, cela devrait être très pratique, mais j'ai ce code comme celui d'autres personnes en tant que tâche et je ne peux pas le modifier. –
De toute façon, le fait de passer le nom et d'autres données dans le constructeur ne fonctionnera pas bien, vous devez passer la totalité de la carte de crédit à la règle sous une forme ou une autre. – Evk