Je vais enregistrer une charge utile de chaîne dans la base de données. J'ai deux configurations globales:
- chiffrement
- compression
Ceux-ci peuvent être activés ou désactivés à l'aide de la configuration de manière à ce que l'un d'eux soit activé, les deux soient activés ou les deux soient désactivés.
Mon implémentation actuelle est la suivante:
if (encryptionEnable && !compressEnable) {
encrypt(data);
} else if (!encryptionEnable && compressEnable) {
compress(data);
} else if (encryptionEnable && compressEnable) {
encrypt(compress(data));
} else {
data;
}
Je pense au motif Décorateur. Est-ce le bon choix ou existe-t-il peut-être une meilleure alternative?
design
design-patterns
object-oriented-design
refactoring
Damith Ganegoda
la source
la source
if
déclarations?Réponses:
Lors de la conception du code, vous avez toujours deux options.
Je ne vais pas me concentrer sur le premier des deux, car il n'y a vraiment rien à dire. Si vous vouliez simplement le faire fonctionner, vous pouvez laisser le code tel quel.
Mais que se passerait-il si vous choisissiez de le faire de manière pédante et résolviez réellement le problème des modèles de conception, comme vous le vouliez?
Vous pourriez envisager le processus suivant:
Lors de la conception du code OO, la plupart des
if
s qui se trouvent dans un code n'ont pas à être là. Naturellement, si vous souhaitez comparer deux types scalaires, tels queint
s oufloat
s, vous êtes susceptible d'avoir unif
, mais si vous souhaitez modifier les procédures en fonction de la configuration, vous pouvez utiliser le polymorphisme pour obtenir ce que vous voulez, déplacer les décisions (leif
s) de votre logique métier à un lieu où les objets sont instanciés - jusqu'aux usines .À partir de maintenant, votre processus peut passer par 4 chemins distincts:
data
n'est ni chiffré ni compressé (appelez rien, retournezdata
)data
est compressé (appelez-lecompress(data)
et retournez-le)data
est crypté (appelez-leencrypt(data)
et retournez-le)data
est compressé et chiffré (appelez-leencrypt(compress(data))
et retournez-le)Juste en regardant les 4 chemins, vous trouvez un problème.
Vous avez un processus qui appelle 3 (théoriquement 4, si vous comptez ne rien appeler comme un) différentes méthodes qui manipulent les données, puis les renvoient. Les méthodes ont des noms différents , différentes soi-disant API publiques (la façon dont les méthodes communiquent leur comportement).
En utilisant le modèle d' adaptateur , nous pouvons résoudre la colision de nom (nous pouvons unifier l'API publique) qui s'est produite. En termes simples, l'adaptateur permet à deux interfaces incompatibles de fonctionner ensemble. De plus, l'adaptateur fonctionne en définissant une nouvelle interface d'adaptateur, que les classes essayant d'unir leur implémentation d'API.
Je vais supposer qu'en ce moment, vous pouvez avoir deux classes responsables de la compression et du chiffrement.
Dans un monde d'entreprise, même ces classes spécifiques sont très susceptibles d'être remplacées par des interfaces, telles que le
class
mot - clé serait remplacé parinterface
(si vous traitez avec des langages comme C #, Java et / ou PHP) ou leclass
mot - clé resterait, mais leCompress
et lesEncrypt
méthodes seraient définies comme un pur virtuel , si vous codez en C ++.Pour faire un adaptateur, nous définissons une interface commune.
Ensuite, nous devons fournir des implémentations de l'interface pour la rendre utile.
En faisant cela, vous vous retrouvez avec 4 classes, chacune faisant quelque chose de complètement différent, mais chacune fournissant la même API publique. La
Process
méthode.Dans votre logique métier, où vous traitez avec la décision none / encryption / compression / both, vous allez concevoir votre objet pour qu'il dépende de l'
DataProcessing
interface que nous avons conçue auparavant.Le processus lui-même pourrait alors être aussi simple que cela:
Plus de conditionnels. La classe
DataService
n'a aucune idée de ce qui sera vraiment fait avec les données lorsqu'elles seront transmises audataProcessing
membre, et il s'en fiche vraiment, ce n'est pas sa responsabilité.Idéalement, vous auriez des tests unitaires testant les 4 classes d'adaptateurs que vous avez créées pour vous assurer qu'elles fonctionnent, vous réussissez votre test. Et s'ils réussissent, vous pouvez être sûr qu'ils fonctionneront, peu importe où vous les appelez dans votre code.
Donc, en procédant de cette façon, je n'aurai plus de
if
s dans mon code?Non. Vous êtes moins susceptible d'avoir des conditions dans votre logique métier, mais elles doivent toujours être quelque part. L'endroit est vos usines.
Et c'est bien. Vous séparez les préoccupations de la création et de l'utilisation réelle du code. Si vous rendez vos usines fiables (en Java, vous pourriez même aller jusqu'à utiliser quelque chose comme le framework Guice de Google), dans votre logique métier, vous n'êtes pas inquiet de choisir la bonne classe à injecter. Parce que vous savez que vos usines fonctionnent et livreront ce qui vous est demandé.
Est-il nécessaire d'avoir toutes ces classes, interfaces, etc.?
Cela nous ramène au début.
Dans la POO, si vous choisissez le chemin pour utiliser le polymorphisme, voulez vraiment utiliser des modèles de conception, voulez exploiter les fonctionnalités du langage et / ou voulez suivre le tout est une idéologie d'objet, alors c'est le cas. Et même alors, cet exemple ne montre même pas toutes les usines que vous allez besoin et si vous deviez refactoriser les
Compression
et lesEncryption
classes et les rendre interfaces à la place, vous devez inclure leurs mises en œuvre aussi bien.En fin de compte, vous vous retrouvez avec des centaines de petites classes et interfaces, axées sur des choses très spécifiques. Ce qui n'est pas nécessairement mauvais, mais pourrait ne pas être la meilleure solution pour vous si tout ce que vous voulez est de faire quelque chose d'aussi simple que d'ajouter deux nombres.
Si vous voulez le faire et rapidement, vous pouvez saisir la solution d'Ixrec , qui a au moins réussi à éliminer les blocs
else if
etelse
, qui, à mon avis, sont même un peu pire qu'une simpleif
.Mise à jour 2: il y a eu une discussion folle sur la première version de ma solution. Discussion principalement provoquée par moi, pour laquelle je m'excuse.
J'ai décidé de modifier la réponse de manière à ce que ce soit l'une des façons de regarder la solution mais pas la seule. J'ai également supprimé la partie décoratrice, où je voulais plutôt la façade, que j'ai finalement décidé de laisser de côté, car un adaptateur est une variation de façade.
la source
Compression
etEncryption
semblent totalement superflues. Je ne sais pas si vous suggérez qu'elles sont en quelque sorte nécessaires au processus de décoration, ou si vous sous-entendez simplement qu'elles représentent des concepts extraits. Le deuxième problème est que créer une classe commeCompressionEncryptionDecorator
conduit au même type d'explosion combinatoire que les conditions de l'OP. Je ne vois pas non plus assez clairement le motif du décorateur dans le code suggéré.Le seul problème que je vois avec votre code actuel est le risque d'explosion combinatoire lorsque vous ajoutez plus de paramètres, ce qui peut être facilement atténué en structurant le code plus comme ceci:
Je ne connais aucun "modèle de conception" ou "idiome" dont cela pourrait être considéré comme un exemple.
la source
else
entre mes deux déclarations if et pourquoi j'attribue àdata
chaque fois. Si les deux indicateurs sont vrais, alors compress () est exécuté, puis encrypt () est exécuté sur le résultat de compress (), comme vous le souhaitez.Je suppose que votre question ne cherche pas à être pratique, auquel cas la réponse de lxrec est la bonne, mais à en apprendre davantage sur les modèles de conception.
Évidemment, le modèle de commande est une exagération pour un problème aussi trivial que celui que vous proposez, mais à des fins d'illustration, il va ici:
Comme vous le voyez, mettre les commandes / transformation dans une liste permet de les exécuter séquentiellement. De toute évidence, il exécutera les deux, ou un seul d'entre eux dépendra de ce que vous mettez dans la liste sans conditions if.
Évidemment, les conditions vont se retrouver dans une sorte d'usine qui rassemble la liste des commandes.
EDIT pour le commentaire de @ texacre:
Il existe de nombreuses façons d'éviter les conditions if dans la partie création de la solution, prenons par exemple une application GUI de bureau . Vous pouvez avoir des cases à cocher pour les options de compression et de cryptage. Dans le
on clic
cas de ces cases à cocher, vous instanciez la commande correspondante et l'ajoutez à la liste, ou supprimez de la liste si vous désélectionnez l'option.la source
commands.add(new EncryptCommand());
oucommands.add(new CompressCommand());
respectivement.Je pense que les "modèles de conception" sont inutilement orientés vers les "modèles oo" et évitent complètement les idées beaucoup plus simples. Nous parlons ici d'un pipeline de données (simple).
J'essaierais de le faire en clojure. Tout autre langage où les fonctions sont de première classe est probablement correct également. Je pourrais peut-être un exemple C # plus tard, mais ce n'est pas aussi agréable. Ma façon de résoudre ce problème serait les étapes suivantes avec quelques explications pour les non-clojuriens:
1. Représentez un ensemble de transformations.
Il s'agit d'une carte, c'est-à-dire d'une table de recherche / dictionnaire / autre, des mots-clés aux fonctions. Un autre exemple (des mots-clés aux chaînes):
Ainsi, l'écriture
(transformations :encrypt)
ou(:encrypt transformations)
retournerait la fonction de cryptage. ((fn [data] ... )
est juste une fonction lambda.)2. Obtenez les options sous forme de séquence de mots clés:
3. Filtrez toutes les transformations à l'aide des options fournies.
Exemple:
4. Combinez les fonctions en une seule:
Exemple:
5. Et puis ensemble:
Le SEUL changement si nous voulons ajouter une nouvelle fonction, disons "debug-print", est le suivant:
la source
funcs-to-run-here (map options funcs)
effectue le filtrage, choisissant ainsi un ensemble de fonctions à appliquer. Je devrais peut-être mettre à jour la réponse et entrer dans un peu plus de détails.[Essentiellement, ma réponse fait suite à la réponse de @Ixrec ci-dessus . ]
Une question importante: le nombre de combinaisons distinctes que vous devez couvrir va-t-il augmenter? Vous connaissez mieux votre domaine. C'est votre jugement.
Le nombre de variantes peut-il éventuellement augmenter? Eh bien, ce n'est pas inconcevable. Par exemple, vous devrez peut-être prendre en charge des algorithmes de chiffrement plus différents.
Si vous prévoyez que le nombre de combinaisons distinctes va augmenter, le modèle de stratégie peut vous aider. Il est conçu pour encapsuler des algorithmes et fournir une interface interchangeable avec le code appelant. Vous auriez toujours une petite quantité de logique lorsque vous créez (instanciez) la stratégie appropriée pour chaque chaîne particulière.
Vous avez indiqué ci - dessus que vous ne vous attendez pas à ce que les exigences changent. Si vous ne vous attendez pas à ce que le nombre de variantes augmente (ou si vous pouvez reporter ce refactoring), gardez la logique telle qu'elle est. Actuellement, vous disposez d'une petite quantité de logique gérable. (Peut-être mettre une note dans les commentaires sur une éventuelle refactorisation d'un modèle de stratégie.)
la source
Une façon de le faire dans scala serait:
L'utilisation d'un modèle de décorateur pour atteindre les objectifs ci-dessus (séparation de la logique de traitement individuelle et de la façon dont ils sont connectés) serait trop verbeuse.
Dans lequel vous auriez besoin d'un modèle de conception pour atteindre ces objectifs de conception dans un paradigme de programmation OO, le langage fonctionnel offre un support natif en utilisant des fonctions de citoyens de première classe (lignes 1 et 2 dans le code) et une composition fonctionnelle (ligne 3)
la source