2016-11-05 1 views
1

Il existe une interface IRule avec une méthode Validate() et plusieurs classes dérivées qui implémentent cette méthode. Les classes sont avec différents cteurs (types et nombre d'arguments). De plus, il existe une interface de base nommée IPaymentProcessor qui doit valider toutes les règles existantes. Ma tâche actuelle consiste à implémenter une abstraction de haut niveau comme factory ou container qui, idéalement, crée toutes les règles avec des constructeurs différents et les renvoie ensuite comme IEnumerable pour itérer et appliquer chaque règle pour la validation de la carte.Usine d'objets avec différents constructeurs

Est-il possible d'effectuer la tâche en utilisant Ninject ou toute autre bibliothèque basée sur la réflexion dans .NET? (AutoFixture, Moq, etc.)

Voici une solution actuelle que je voulais améliorer.

public interface IRule 
{ 
     bool Validate(); 
} 

    class Rule1 : IRule 
    { 
     public Rule1(string name) { ... } 
     bool Validate() { ... } 
    } 

    class Rule2 : IRule 
    { 
     public Rule1(int month, int year) { ... } 
     bool Validate() { ... } 
    } 

    interface IPaymentProcessor 
    { 
    bool MakePayment(CreditCard card); 
    } 

    class MyPaymentProcess : IPaymentProcessor 
    { 
    public bool MakePayment(CreditCard card) 
    { 
     // Here is pitfall. If we need to add/remove another rule or one of 
     // ctors changed, then we have to edit this place, which isn't flexible 
     var rules = new List<IBusinessRule>() { new Rule1(card.Name), new Rule2(card.Month, card.Year) }; 
     foreach(var r in rules) if(!r.Validate()) { return false; } 
     return true; 
    } 
    } 
+0

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

+0

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. –

+0

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

Répondre

0

Il semble en fin de compte que vous voulez valider une carte de crédit, donc je ne comprends pas vraiment pourquoi vous créez des règles différentes pour valider les propriétés spécifiques de la carte de crédit et ne pas appliquer une règle générale qui valide la carte comme un ensemble. Si, pour des raisons que je ne connais pas, vous devez créer des règles qui valident indépendamment le nom, la date d'expiration, le numéro, etc. Vous avez toujours un moyen très simple de le faire; il suffit de passer une carte dans le constructeur et de laisser chaque règle valider l'information à laquelle elle est supposée appartenir;

public NameRule(CreditCard card) { ... } 
public bool Validate() => !string.IsNullOrEmpty(card.Name); 

Une solution plus propre et celui que vous auriez besoin d'utiliser si vous ne saviez pas la carte que vous seriez en train de valider lors de la création des règles, qui se passe dans le constructeur un Predicate<CreditCard>. Dans ce cas, vous même pas besoin de plus d'un type de règle:

var nameRule = new Rule<CreditCard>(c => !string.IsNullOrEmpty(c.Name)); 
var dateRule = new Rule<CreditCard>(c => c.Date > DateTime. 

Étant la mise en œuvre de Rule:

public class Rule<T> 
{ 
    private readonly Predicate<T> myPredicate; 
    public Rule(Predicate<T> predicate) 
    { 
     myPredicate = predicate; 
    } 

    public bool Validate(CreditCard card) => myPredicate(card); 
} 
+0

Merci pour les conseils. C'est une tâche académique de montrer la connaissance en DI, la dépendance de classe basse, les principes SOLIDES etc. –

+0

@DenisK. J'ai mis à jour la réponse avec ce que je considère comme la meilleure solution; vous n'avez pas vraiment besoin de types de règles différents, les instances spécialisées suffisent. – InBetween

+0

J'ai l'idée, il n'y a aucun moyen de modifier les classes existantes qui est Rule1, Rule2. Ils devraient rester tels quels. Bien que, une partie de votre solution, j'ai ajouté à ma nouvelle classe distincte RulesFactory. Lorsque des créateurs d'instance IRule existants (Func ) s'enregistrent puis retournent tous les créateurs enregistrés sur demande. La solution publiera ci-dessous un peu plus tard. –

0

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.