Du livre professionnel entreprise .net , qui a 5 étoiles sur Amazon que je doute après avoir lu. Voici une classe d'emprunteur (en C # mais c'est assez basique; tout le monde peut le comprendre) qui fait partie d'une application hypothécaire Les auteurs ont construit:
public List<BrokenBusinessRule> GetBrokenRules()
{
List<BrokenBusinessRule> brokenRules = new List<BrokenBusinessRule>();
if (Age < 18)
brokenRules.Add(new BrokenBusinessRule("Age", "A borrower must be over 18 years of age"));
if (String.IsNullOrEmpty(FirstName))
brokenRules.Add(new BrokenBusinessRule("FirstName", "A borrower must have a first name"));
if (String.IsNullOrEmpty(LastName))
brokenRules.Add(new BrokenBusinessRule("LastName", "A borrower must have a last name"));
if (CreditScore == null)
brokenRules.Add(new BrokenBusinessRule("CreditScore", "A borrower must have a credit score"));
else if (CreditScore.GetBrokenRules().Count > 0)
{
AddToBrokenRulesList(brokenRules, CreditScore.GetBrokenRules());
}
if (BankAccount == null)
brokenRules.Add(new BrokenBusinessRule("BankAccount", "A borrower must have a bank account defined"));
else if (BankAccount.GetBrokenRules().Count > 0)
{
AddToBrokenRulesList(brokenRules, BankAccount.GetBrokenRules());
}
// ... more rules here ...
return brokenRules;
}
liste de code complet sur snipt.org .
Ce qui me confondre est que le livre est censé être sur la conception d'entreprise professionnelle. Peut-être que je suis un peu biaisé parce que l'auteur avoue au chapitre 1 qu'il ne savait pas vraiment quelle était la découplage ou quoi SOLID signifiait jusqu'à la 8ème année de sa carrière de programmation (et je pense qu'il a écrit le livre en année 8.1)
Je ne suis pas expert mais je ne me sens pas à l'aise avec:
Trop nombreux si d'autres déclarations.
La classe des deux sert d'entité et a une validation. N'est-ce pas un design malodorant? (Vous devrez peut-être avoir besoin de voir la classe complète pour obtenir un contexte)
Peut-être que je me trompe, mais je ne veux pas récupérer de mauvaises pratiques d'un livre qui est censé enseigner une conception d'entreprise. Le livre regorge d'extraits de code similaires et c'est vraiment vous déranger maintenant. Si elle est Mauvaise conception, comment pouvez-vous éviter d'utiliser trop de déclarations si sinon.
Je ne m'attends évidemment pas que vous réécrivez la classe, il suffit de fournir une idée générale de ce qui pourrait être fait.
Je suis un développeur .NET, alors j'ai écrit cela dans le Bloc-notes à l'aide de la syntaxe C # (qui pourrait être identique à Java).
public class BorrowerValidator
{
private readonly Borrower _borrower;
public BorrowerValidator(Borrower borrower)
{
_borrower = borrower;
Validate();
}
private readonly IList<ValidationResult> _validationResults;
public IList<ValidationResult> Results
{
get
{
return _validationResults;
}
}
private void Validate()
{
ValidateAge();
ValidateFirstName();
// Carry on
}
private void ValidateAge()
{
if (_borrower.Age < 18)
_validationResults.Add(new ValidationResult("Age", "Borrower must be over 18 years of age");
}
private void ValidateFirstname()
{
if (String.IsNUllOrEmpty(_borrower.Firstname))
_validationResults.Add(new ValidationResult("Firstname", "A borrower must have a first name");
}
}
Usage:
var borrower = new Borrower
{
Age = 17,
Firstname = String.Empty
};
var validationResults = new BorrowerValidator(borrower).Results;
Il n'y a pas de point en passant borrower
objet à ValidateAge
et ValidateFirstName
, le stockez simplement comme champ.
Pour prendre les choses plus loin, vous pouvez extraire une interface: IBorrowerValidator
. Dites que vous avez alors différentes façons de valider vos emprunteurs en fonction de leur situation financière. Par exemple, si quelqu'un est ex-faillite, vous pouvez avoir:
public class ExBankruptBorrowerValidator : IBorrowerValidator
{
// Much stricter validation criteria
}
Je ne suis pas expert mais je ne me sens pas à l'aise avec
1- Trop nombreux si d'autres déclarations.
2- La classe est à la fois en tant qu'entité et a une validation. N'est-ce pas un design malodorant?
Votre intuition est sur place ici. C'est un design faible.
Le code est destiné à être un validateur de règles d'entreprise . Maintenant, imaginez que vous travailliez pour une banque et que la politique est modifiée de devoir avoir 18 ans, de devoir avoir 21 ans, mais s'il y a un co-signataire agréé, un enfant de 18 ans est autorisé, à moins que le co-signataire ne vit pas Maryland, auquel cas ...
Vous voyez où je vais ici. Les règles changent tout le temps dans les banques et les règles ont des relations complexes les unes avec les autres. Par conséquent, ce code est une mauvaise conception car elle nécessite un programmeur de réécrire le code à chaque fois qu'une stratégie change. L'intention du code donné est de produire une raison pour laquelle une politique a été violée et c'est génial, mais dans cette affaire les politiques doivent être des objets, pas si des déclarations, et Les stratégies doivent être chargées dans une base de données , pas codée dur dans la validation de la construction d'un objet.
Enha. Normalement, ce type de chose doit être considéré comme une odeur de code, mais dans ce cas particulier, je pense que c'est bien. Oui, vous pourriez rechercher des annotations, mais ils ont une douleur et une moitié pour déboguer. Oui, vous pouvez chercher à déplacer la validation à une classe différente, mais ce type de validation est peu susceptible de varier ou d'être réutilisé pour d'autres types.
Cette mise en œuvre est efficace, facile à tester et facile à entretenir. Pour de nombreuses applications, cette conception va frapper le bon équilibre des compromis de conception. Pour d'autres, plus de découplage vaut peut-être l'ingénierie supplémentaire.
Une autre chose concernant le point 2 de votre question: supprimer la logique de validation de cette classe (ou toute autre logique qui ne servit pas l'objectif de la classe d'une entité) supprime complètement la logique commerciale. Cela vous laisse avec un modèle de domaine anémique , qui est principalement considéré comme un anti-motif.
EDIT: Bien sûr, je suis pleinement d'accord à Eric Lippert cette logique commerciale spécifique susceptible d'être modifiée ne doit souvent pas être corrigée dans l'objet. Cela ne signifie toujours pas que c'est un bon design à supprimer toute logique commerciale d'objets métier, mais ce code de validation particulier est un bon candidat à l'abeille extraite à un endroit où il peut être changé de l'extérieur.
Oui, c'est une mauvaise odeur, mais la solution est facile. Vous avez une liste de bêties, mais tout ce que vous avez brisé est, autant que je sache, est un nom et une chaîne explicative. Vous avez cela à l'envers. Voici une façon de le faire (s'il vous plaît noter: Ceci est hors du sommet de ma tête et je n'ai pas impliqué de nombreuses pensées de conception - je suis sûr que quelque chose de mieux pourrait être fait avec plus de pensée):
Ensuite, toutes ces déclarations si-ele-else peuvent être remplacées par une simple boucle quelque chose comme ceci:
for (BusinessRule rule: businessRules) {
RuleCheckResult result = rule.check(borrower);
if (!result.passed) {
for (RuleFailureReason reason: result.getReasons()) {
BrokenRules.add(rule.getName(), reason.getExplanation())
}
}
Ou vous pouvez le rendre plus simple et avoir cela
for (BusinessRule rule: businessRules) {
RuleCheckResult result = rule.check(borrower);
if (!result.passed) {
BrokenRules.add(rule.getName(), result)
}
}
Il devrait être assez évident sur la manière dont RulecheckResult et SleeDeason pourrait être composé.
Vous pouvez maintenant avoir des règles complexes avec plusieurs critères ou règles simples sans aucune. Mais vous n'essayez pas de représenter tous ceux d'un long ensemble d'IFS et de SI-ESSES et vous n'avez pas à réécrire l'objet de l'emprunteur à chaque fois que vous proposez une nouvelle règle ou laissez tomber un ancien.
Je pense que dans ces types de scénarios de règles, un bon modèle à appliquer est le modèle de spécification . Cela crée non seulement un moyen plus élégant d'encapsuler une règle d'entreprise individuelle, il le fait également de manière à ce que la règle individuelle puisse être combinée à d'autres règles pour créer des règles plus complexes. Tout sans (IMHO) nécessitant une infrastructure excessive. La page Wikipedia fournit également une implémentation de C # assez impressionnante que vous pouvez utiliser dès la sortie de la boîte (bien que tout ce que je vous recommande de le comprendre et non simplement l'aveuglément).
Pour utiliser un exemple modifié de la page Wikipedia:
ValidAgeSpecification IsOverEighteen = new ValidAgeSpecification();
HasBankAccountSpecification HasBankAccount = new HasBankAccountSpecification();
HasValidCreditScoreSpecification HasValidCreditScore = new HasValidCreditScoreSpecification();
// example of specification pattern logic chaining
ISpecification IsValidBorrower = IsValidAge.And(HasBankAccount).And(HasValidCreditScore);
BorrowerCollection = Service.GetBorrowers();
foreach (Borrower borrower in BorrowerCollection ) {
if (!IsValidBorrower.IsSatisfiedBy(borrower)) {
validationMessages.Add("This Borrower is not Valid!");
}
}
Vous pouvez dire "Cet exemple particulier n'autorise pas les règles individuelles étant cassées!". Le point ici est de démontrer comment les règles peuvent être composées. Vous pouvez aussi facilement déplacer toutes les 4 règles (y compris le composite) dans une collection de ISpecification
et itérer sur eux comme ITHOBRECE a démontré.
Plus tard, lorsque les UPS plus élevées souhaitent effectuer un nouveau programme de prêt qui n'est disponible que pour les adolescents ayant un cosignataire, créant une règle comme celle-ci est facile et plus déclarative. De plus, vous pouvez réutiliser votre règle d'âge existante!
ISpecification ValidUnderageBorrower = IsOverEighteen.Not().And(HasCosigner);
Ce n'est pas réutilisable du tout. J'utiliserais Data Annotations
Ou cadre de validation basé sur l'attribut similaire. Vérifiez http://odetocode.com/blogs/scott/archive/2011/06/29/manual-validation-with-data-annotations.aspx Par exemple.
EDIT: Mon propre exemple
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
namespace ValidationTest
{
public interface ICategorized
{
[Required]
string Category { get; set; }
}
public abstract class Page
{
[Required]
[MaxLength(50)]
public string Slug { get; set; }
[Required]
public string Title { get; set; }
public string Text { get; set; }
public virtual ICollection<ValidationResult> Validate()
{
var results = new List<ValidationResult>();
ValidatePropertiesWithAttributes(results);
ValidatePropertiesFromInterfaces(results);
return results;
}
protected void ValidatePropertiesWithAttributes(ICollection<ValidationResult> results)
{
Validator.TryValidateObject(this, new ValidationContext(this,null,null), results);
}
protected void ValidatePropertiesFromInterfaces(ICollection<ValidationResult> results)
{
foreach(var iType in this.GetType().GetInterfaces())
{
foreach(var property in iType.GetProperties())
{
foreach (var attr in property.GetCustomAttributes(false).OfType<ValidationAttribute>())
{
var value = property.GetValue(this);
var context = new ValidationContext(this, null, null) { MemberName = property.Name };
var result = attr.GetValidationResult(value, context);
if(result != null)
{
results.Add(result);
}
}
}
}
}
}
public class Blog : Page
{
}
public class Post : Page, ICategorized
{
[Required]
public Blog Blog { get; set; }
public string Category { get; set; }
public override ICollection<ValidationResult> Validate()
{
var results = base.Validate();
// do some custom validation
return results;
}
}
}
Il est facile de modifier pour inclure/exclure les propriétés ValidatePropertiesWithAttributes(string[] include, string[] exclude)
ou ajouter un fournisseur de règles pour obtenir des règles à partir de DB Validate(IRuleProvider provider)
etc.
La question clé ici est "est-ce le moyen le plus lisible, clair et consistible à mettre en œuvre la logique commerciale".
Dans le cas, je pense que la réponse est oui.
Toutes les tentatives de scission du code ne masquent que l'intention sans rien ajouter autre qu'un nombre réduit de "si"
Il s'agit d'une liste de tests ordonnés et d'une séquence de déclarations "si" semblerait être la meilleure mise en œuvre. Toute tentative de faire quelque chose d'intelligent sera plus difficile à lire et à pire que vous ne pourrez peut-être pas mettre en œuvre une nouvelle règle dans une implémentation "plus jolie" plus abstraite.
Les règles commerciales peuvent être vraiment désordonnées et il n'y a souvent pas de moyen "propre" de les mettre en œuvre.