Question

Je me demande si le modèle spécification est inutile, étant donné l'exemple suivant:

Dites que vous voulez vérifier si un équilibre client a assez dans sa / son compte, vous devez créer une spécification de quelque chose comme:

new CustomerHasEnoughMoneyInTheirAccount().IsSatisfiedBy(customer)

Cependant, ce que je me demande est que je peux obtenir les mêmes « avantages » de modèle Spécification (par exemple seulement besoin de modifier les règles métier sur la place) en utilisant getter Propriété dans la classe client comme ceci:

public class Customer
{

     public double Balance;

     public bool HasEnoughMoney
     {
          get { return this.Balance > 0; }
     }
}

à partir du code client:

customer.HasEnoughMoney

Alors, ma question est vraiment; quelle est la différence entre l'utilisation du getter de la propriété pour envelopper la logique métier et la création de la classe Spécification?

Merci à vous tous à l'avance!

Était-ce utile?

La solution

Parce que la classe de spécification, vous pouvez créer de nouveaux critères sans modification des objets eux-mêmes.

Autres conseils

Dans le sens général, un objet de spécification est juste un prédicat enveloppé dans un objet. Si un prédicat est très couramment utilisé avec une classe, il peut être judicieux de Déplacer Méthode le prédicat dans la classe qu'elle applique.

Ce modèle est vraiment dans son propre quand vous construisez quelque chose plus compliqué comme ceci:

var spec = new All(new CustomerHasFunds(500.00m),
                   new CustomerAccountAgeAtLeast(TimeSpan.FromDays(180)),
                   new CustomerLocatedInState("NY"));

et en le passant autour de ou sérialisation il; il peut faire encore plus de sens quand vous fournissez une sorte de « spécifications constructeur » interface utilisateur.

Cela dit, C # fournit des moyens plus idiomatiques d'exprimer ce genre de choses, telles que les méthodes d'extension et LINQ:

var cutoffDate = DateTime.UtcNow - TimeSpan.FromDays(180); // captured
Expression<Func<Customer, bool>> filter =
    cust => (cust.AvailableFunds >= 500.00m &&
             cust.AccountOpenDateTime >= cutoffDate &&
             cust.Address.State == "NY");

Je joue autour avec un code expérimental qui met en œuvre des spécifications en termes de Expressions, avec statique très simple constructeur méthodes.

public partial class Customer
{
    public static partial class Specification
    {
        public static Expression<Func<Customer, bool>> HasFunds(decimal amount)
        {
            return c => c.AvailableFunds >= amount;
        }

        public static Expression<Func<Customer, bool>> AccountAgedAtLeast(TimeSpan age)
        {
            return c => c.AccountOpenDateTime <= DateTime.UtcNow - age;
        }


        public static Expression<Func<Customer, bool>> LocatedInState(string state)
        {
            return c => c.Address.State == state;
        }
    }
}

Cela dit, c'est une charge de toute boilerplate qui n'ajoute pas de valeur! Ces Expressions ne regarder que des propriétés publiques, de sorte que l'on pourrait tout aussi bien utiliser une ancienne plaine lambda! Maintenant, si l'un de ces spécifications des besoins à l'état non-accès public, nous avons vraiment ne besoin d'une méthode de constructeur avec l'accès à l'état non-public. Je vais utiliser lastCreditScore comme un exemple ici.

public partial class Customer
{
    private int lastCreditScore;

    public static partial class Specification
    { 
        public static Expression<Func<Customer, bool>> LastCreditScoreAtLeast(int score)
        {
            return c => c.lastCreditScore >= score;
        }
    }
}

Il faut aussi un moyen de faire un composite de ces spécifications - dans ce cas, un composite qui exige que tous les enfants pour être vrai:

public static partial class Specification
{
    public static Expression<Func<T, bool>> All<T>(params Expression<Func<T, bool>>[] tail)
    {
        if (tail == null || tail.Length == 0) return _0 => true;
        var param = Expression.Parameter(typeof(T), "_0");
        var body = tail.Reverse()
            .Skip(1)
            .Aggregate((Expression)Expression.Invoke(tail.Last(), param),
                       (current, item) =>
                           Expression.AndAlso(Expression.Invoke(item, param),
                                              current));

        return Expression.Lambda<Func<T, bool>>(body, param);
    }
}

Je pense qu'une partie de l'inconvénient est qu'il peut entraîner des arbres Expression complexes. Par exemple, la construction de ceci:

 var spec = Specification.All(Customer.Specification.HasFunds(500.00m),
                              Customer.Specification.AccountAgedAtLeast(TimeSpan.FromDays(180)),
                              Customer.Specification.LocatedInState("NY"),
                              Customer.Specification.LastCreditScoreAtLeast(667));

produit un arbre Expression qui ressemble à ceci. (Ceux-ci sont légèrement versions formatées de ce qui revient ToString() lorsqu'il est appelé sur la Expression - note que vous ne seriez pas en mesure de voir la structure de l'expression du tout si vous aviez seulement un simple délégué Quelques notes: un DisplayClass est classe généré par le compilateur qui détient des variables locales capturées dans une fermeture, pour faire face à la vers le haut funarg problème ;. et l'objet d'un dumping Expression utilise un seul signe = pour représenter la comparaison de l'égalité, plutôt que C # 's == typique)

_0 => (Invoke(c => (c.AvailableFunds >= value(ExpressionExperiment.Customer+Specification+<>c__DisplayClass0).amount),_0)
       && (Invoke(c => (c.AccountOpenDateTime <= (DateTime.UtcNow - value(ExpressionExperiment.Customer+Specification+<>c__DisplayClass2).age)),_0) 
           && (Invoke(c => (c.Address.State = value(ExpressionExperiment.Customer+Specification+<>c__DisplayClass4).state),_0)
               && Invoke(c => (c.lastCreditScore >= value(ExpressionExperiment.Customer+Specification+<>c__DisplayClass6).score),_0))))

salissant! Beaucoup d'invocation de lambdas immédiates et références retenues aux fermetures créées dans les méthodes de constructeur. En remplaçant les références de fermeture avec leurs valeurs capturées et ß- réduisant la lambdas imbriquées (je aussi a-convertis tous les noms de paramètres uniques à les symboles générés comme une étape intermédiaire de ß-réduction Simplify), on obtient un arbre Expression beaucoup plus simple:

_0 => ((_0.AvailableFunds >= 500.00)
       && ((_0.AccountOpenDateTime <= (DateTime.UtcNow - 180.00:00:00))
           && ((_0.Address.State = "NY")
               && (_0.lastCreditScore >= 667))))

Ces arbres Expression peuvent ensuite être combinées, compilées en délégués, joli imprimé, édité, passé aux interfaces LINQ qui comprennent les arbres Expression (tels que ceux fournis par EF), ou ce que vous avez.

Sur une note de côté, je construit un petit micro-référence stupide et en fait découvert que l'élimination de la référence de fermeture a eu un impact remarquable performance sur la vitesse de l'évaluation de l'exemple Expression lorsqu'il est compilé à un délégué - il réduit le temps d'évaluation presque en la moitié (!), de 134.1ns à 70.5ns par appel sur la machine que j'arrive d'être assis devant. D'autre part, ß-réduction ne fait aucune différence détectable, peut-être parce que la compilation fait cela de toute façon. Dans tous les cas, je doute un ensemble classique spécification de classe pourrait atteindre ce genre de vitesse d'évaluation d'un composite de quatre conditions; si une telle classe classiqueensemble devait être construit pour d'autres raisons telles que la commodité d'un code constructeur-UI, je pense qu'il serait souhaitable d'avoir l'ensemble de la classe produire une Expression plutôt que d'évaluer directement, mais d'abord si vous avez besoin du modèle tout en C # - Je l'ai vu beaucoup trop de code de spécification-surdosé.

Oui, il est inutile.

L'article de Wikipedia critique ce modèle longuement. Mais je vois la plus grande critique étant uniquement Inner-plateforme Effet . Pourquoi réinventer l'opérateur? Encore une fois, lisez l'article de Wikipedia pour la critique plus complète.

Henry, vous avez raison d'assumer la propriété Get est supérieure. Pourquoi un concept Évitez l'OO plus simple, bien compris, pour un « modèle » obscur qui, dans sa conception, ne répond pas à votre question très? C'est une idée, mais une mauvaise. Il est un anti-modèle, un modèle qui fonctionne contre vous, le codeur.

Vous avez demandé quelle est la différence, mais une question plus utile est, quand doit être utilisé un motif de spécification?

Ne jamais utiliser ce modèle , est ma règle générale pour ce motif.

Tout d'abord, vous devez réaliser ce n'est pas une théorie générique, il est seulement un modèle spécifique; avec une modélisation spécifique des classes {Spécification, AndSpecification, ...}. Avec la théorie plus générale dirigée par le domaine à l'esprit, vous pouvez abandonner ce modèle, et ont encore des options supérieures que tout le monde connaît:. Par exemple, des objets / méthodes / propriétés bien nommées à la langue de domaine du modèle et de la logique

Jeffrey dit:

  

un objet de spécification est juste un prédicat enveloppé dans un objet

C'est vrai pilotée par le domaine, mais pas le modèle de spécification spécifique. Jeffrey, décrit en détail une situation où l'on peut vouloir construire dynamiquement une expression IQueryable, de sorte qu'il peut exécuter efficacement sur la banque de données (base de données SQL). Sa conclusion finale est que vous ne pouvez pas le faire avec le motif Spécification comme il est prescrit. arbres d'expression IQueryable Jeffrey sont une alternative aux règles logiques isoler et de les appliquer dans différents matériaux composites. Comme vous pouvez le voir dans son exemple de code, il est bavard et très difficile à travailler. Je ne peux imaginer une situation qui nécessiterait soit ces composites dynamiques. Et si nécessaire, il y a beaucoup d'autres techniques disponibles qui sont plus simples.

Nous savons tous que vous devez optimiser la performance passée. Toute tentative ici pour obtenir bleeding edge avec des arbres d'expression IQueryable, est un piège. Au lieu de cela, commencer par les meilleurs outils, un simple et laconique propriété Getter premier. Ensuite, tester, évaluer et hiérarchiser ce qui reste de travail.

Je suis encore à l'expérience d'une situation où ce modèle est nécessaire Spécification / mieux. Comme je viens dans des situations supposées, je vais les énumérer ici et les Rebut. Si je tombe sur une bonne situation, je vais revoir cette réponse avec une nouvelle section.

RE: zerkms réponse

  

Parce que la classe de spécification, vous pouvez créer de nouveaux critères [sic]   sans modification des objets eux-mêmes.

C # déjà pour de telles situations Caters:

  • L'héritage (en général), où vous étendez la classe héritée (ce qui est bien quand vous ne possédez pas l'espace de noms / bibliothèque d'où vient la classe) puis
  • méthode redéfinie dans Inheritence
  • partiel - super quand vous avez des classes de données modèle. Vous pouvez ajouter [NotStored] propriétés situées le long et profiter de tout le bonheur d'accéder aux informations dont vous avez besoin directement sur l'objet. Lorsque vous appuyez sur « » IntelliSense vous dit ce que les membres sont disponibles.
  • Méthodes d'extension sont grands lorsque l'héritage est pas pratique (architecture ne le supporte pas), ou si la classe parente est scellé.

Dans les projets je prends plus de, je rencontre des anti-modèles comme modèle Spécification, et plus encore. Ils sont souvent dans un projet / Bibliothèque séparée (sur la fragmentation des projets est un autre mauvais pratglace) et tout le monde est trop peur d'étendre les objets.

Voir la réponse de, plus: une spécification peut aussi fonctionner sur les types abstraits comme les interfaces ou comme générique prise applicable à toute une gamme d'objets.

Ou le chèque doit être fait sur le client pourrait dépendre du contexte. Par exemple, un objet client pourrait ne pas être valide pour le système encore, mais valable pour l'enregistrer dans la base de données de rôle paie au milieu d'un processus de traitement ultérieur lorsque l'utilisateur se connecte à nouveau. Avec les spécifications, vous pouvez créer des groupes de contrôles connexes dans un emplacement centralisé et passer à l'ensemble en fonction du contexte. Dans cette situation, vous souhaitez combiner avec un modèle d'usine par exemple.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top