web-dev-qa-db-fra.com

Suggéré OO Modèle de design pour un constructeur de requêtes

Essentiellement, je construis je construis un constructeur de requêtes (en C #, si cela est pertinent) qui a ce type d'interface, exposant des fonctions davantage typées statiquement car la requête devient plus raffinée.

AnimalQueryBuilder
 - HasId(id): AnimalQueryBuilder
 - IsHealthy(): AnimalQueryBuilder
 - IsDog(): DogQueryBuilder
 - IsCat(): CatQueryBuilder

DogQueryBuilder
 - NumberOfPuppies(count): DogQueryBuilder
 - TailLengthBetween(start, end): DogQueryBuilder

CatQueryBuilder
 - AverageMeowsPerDayBetween(start, end): CatQueryBuilder
 - LikesMilk(): CatQueryBuilder

Essentiellement, à partir d'un animalquerybuilder, je souhaite pouvoir faire les actions suivantes, pour construire une requête de manière fluide:
var builder = new AnimalQueryBuilder().IsHeathy().IsDog().NumberOfPuppies(4);

Cela fonctionne actuellement, mais signifie que vous ne pouvez pas structurer la requête comme suit:
var builder = new AnimalQueryBuilder().IsDog().NumberOfPuppies(4).HasId(21);
[.____] parce que HasId n'est pas exposé sur le dogquerybuilder

La façon dont je peux obtenir cela pour que vous puissiez travailler actuellement est d'avoir DogquereryBuilder et CatqueryBuilder hériter de l'animalquerybuilder, mais vous pourrez alors faire des actions invalides, comme:
var builder = new AnimalQueryBuilder().IsDog().NumberOfPuppies(4).IsCat();

  • Existe-t-il un modèle de conception qui existe déjà pour quelque chose comme ça?
  • Devrait-il simplement descendre le premier itinéraire de ne pas exposer des fonctions supérieures (HASID), plus il est affiné et appliquer ce type d'ordre?
6
James B.

Juste quelques observations à l'avance:

  1. On dirait que vous construisez un prédicat plutôt qu'une requête complète qui spécifie les champs et les sources de données.
  2. Votre approche de conception ne permet pas de prédicats qui sont des arbres d'expression, par exemple avec and et or.

Comme d'autres l'ont souligné, il semble que vous soyez quelque part entre un modèle fluide et un modèle de constructeur, mais ceux-ci ne peuvent pas expliquer pleinement la gamme complète des prédicats dont vous pourriez avoir besoin.

L'utilisation d'un design fluide signifie que vous ne pouvez affiner que ce que vous avez déjà fait. Lorsque vous utilisez le modèle de conception du constructeur en particulier, il est rare de retourner quelque chose "plus petit", par exemple, de retourner DogQueryBuilder de IsDog(), plutôt que de renvoyer une mise à jour AnimalQueryBuilder.


Une autre considération importante est la sémantique de la requête une fois que vous avez construite.

Par exemple:

  • Est-ce que cela construira une chaîne de requête à passer à certains SGBD?
  • Aurez-vous besoin d'extraire des métadonnées de celui-ci, par exemple une liste de tous les domaines de référence?
  • Aurez-vous déjà besoin d'une expression qui fait référence à des sources de données distinctes qui ont été rejointes ailleurs dans la requête? Par exemple, "le chien et son propriétaire ont le même nom."

Les réponses devraient finalement déterminer le type de mise en œuvre avec laquelle vous accédez. Des solutions complètes pourraient exister pour un SGBD spécifique, mais une encapsulation négligente pourrait sérieusement compliquer les tests d'unités ou échanger le SGBD plus tard.

Ces solutions manquent généralement également de sécurité du type de compilation (vous ne trouverez pas une solution existante qui a Cat et Dog types), il est donc généralement utile d'encapsuler toujours des constructeurs de requêtes natives avec quelque chose qui est fortement typé.


Globalement, j'éviterais le modèle de conception du constructeur pour le prédicat complet, car seulement les prédicats les plus simples ne sont pas arbres d'expression.

Dans votre exemple, lorsque vous dites .IsDog().NumberOfPuppies(4), NumberOfPuppies est indiqué sur IsDog. Dans la langue du plan, cela pourrait signifier au moins deux choses différentes:

  1. "si c'est un chien, Diminuer-le à un chien Ensuite, assurez-vous qu'il dispose de 4 chiots."
  2. "-Ça doit être un chien. Étant donné que c'est, Diminuer-le à un chien et assurez-vous qu'il dispose de 4 chiots."

Aucune de ces interprétations n'est déraisonnable, mais ce n'est pas clair, ce qu'il est. La seconde est clairement un raffinement du premier et pourrait donc prendre la forme de "(ce doit être un chien) and (s'il s'agit d'un chien ...)."


En résumé, votre solution actuelle semble affiner les conditions sur des champs de données spécifiques sans permettre la spécification de plusieurs conditions. Un motif fluide convient parfaitement à cibler un champ particulier (en quelque sorte comme un getter), mais vous aurez probablement besoin d'opérations booléennes.

Par exemple:

Predicate
 - And(Predicate...): Predicate [static]
 - Or(Predicate...):  Predicate [static]
 - Not(Predicate):    Predicate [static]

Ensuite, laissez les constructeurs individuels chacun construit un Predicate.

Predicate.And(
  new AnimalPredicateBuilder().IsDog().build(),
  new AnimalPredicateBuilder().AsDog().NumberOfPuppies(4).build(),
  new AnimalPredicateBuilder().HasId(21).build());

Les méthodes membres de Predicate _ _ vont alors dépendre de différentes manières dont vous aurez besoin de l'utiliser, par exemple créer une requête pour SQLite, collectant une liste de champs informatiques.

1
Kevin P. Barry