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();
Juste quelques observations à l'avance:
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:
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:
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.