Cette question est un peu agnostique au langage, mais pas complètement, car la programmation orientée objet (OOP) est différente, par exemple, en Java , qui n'a pas de fonctions de première classe, que ce n'est le cas en Python .
En d’autres termes, je me sens moins coupable de créer des classes inutiles dans un langage comme Java, mais j’ai l’impression qu’il existe peut-être une meilleure solution dans les langages moins cinglés tels que Python.
Mon programme doit effectuer plusieurs fois une opération relativement complexe. Cette opération nécessite beaucoup de "comptabilité", doit créer et supprimer des fichiers temporaires, etc.
C'est pourquoi il doit également faire appel à beaucoup d'autres "sous-opérations" - mettre tout en une seule méthode n'est pas très agréable, modulaire, lisible, etc.
Maintenant ce sont des approches qui me viennent à l'esprit:
1. Créez une classe qui ne possède qu'une méthode publique et conserve l'état interne nécessaire aux sous-opérations dans ses variables d'instance.
Cela ressemblerait à ceci:
class Thing:
def __init__(self, var1, var2):
self.var1 = var1
self.var2 = var2
self.var3 = []
def the_public_method(self, param1, param2):
self.var4 = param1
self.var5 = param2
self.var6 = param1 + param2 * self.var1
self.__suboperation1()
self.__suboperation2()
self.__suboperation3()
def __suboperation1(self):
# Do something with self.var1, self.var2, self.var6
# Do something with the result and self.var3
# self.var7 = something
# ...
self.__suboperation4()
self.__suboperation5()
# ...
def suboperation2(self):
# Uses self.var1 and self.var3
# ...
# etc.
Le problème que je vois avec cette approche est que l'état de cette classe n'a de sens que de manière interne et qu'il ne peut rien faire avec ses instances, à l'exception d'appeler leur seule méthode publique.
# Make a thing object
thing = Thing(1,2)
# Call the only method you can call
thing.the_public_method(3,4)
# You don't need thing anymore
2. Créez un ensemble de fonctions sans classe et transmettez entre elles les différentes variables nécessaires en interne (sous forme d'arguments).
Le problème que je vois avec ceci est que je dois passer beaucoup de variables entre les fonctions. En outre, les fonctions seraient étroitement liées les unes aux autres, mais elles ne seraient pas regroupées.
3. Comme 2. mais rendez les variables d'état globales au lieu de les transmettre.
Ce ne serait pas bon du tout, car je dois faire l'opération plus d'une fois, avec des entrées différentes.
Y a-t-il une quatrième meilleure approche? Si non, laquelle de ces approches serait la meilleure et pourquoi? Y a-t-il quelque chose qui me manque?
la source
Réponses:
L'option 1 est un bon exemple d' encapsulation utilisée correctement. Vous voulez que l'état interne soit caché du code extérieur.
Si cela signifie que votre classe n'a qu'une méthode publique, ainsi soit-il. Ce sera tellement plus facile à maintenir.
En POO, si vous avez une classe qui fait exactement 1 chose, a une petite surface publique et garde tout son état interne caché, alors vous êtes (comme dirait Charlie Sheen) GAGNANT .
L'option 2 souffre d'une faible cohésion . Cela rendra la maintenance plus difficile.
L'option 3, comme l'option 2, souffre d'une faible cohésion, mais beaucoup plus sévèrement!
L’histoire a montré que la commodité des variables globales est compensée par le coût brutal de maintenance qu’elle entraîne. C'est pourquoi vous entendez de vieux pets comme moi parler sans cesse de l'encapsulation.
L'option gagnante est n ° 1 .
la source
ThingDoer(var1, var2).do_it()
par rapport àdo_thing(var1, var2)
.def do_thing(var1, var2): return _ThingDoer(var1, var2).run()
pour rendre l’API externe un peu plus jolie.Je pense que # 1 est en fait une mauvaise option.
Considérons votre fonction:
Quelles sont les données utilisées par suboperation1? Recueille-t-il les données utilisées par suboperation2? Lorsque vous transmettez des données en les stockant sur votre ordinateur, je ne peux pas dire comment les éléments de fonctionnalité sont liés. Lorsque je regarde moi-même, certains attributs proviennent du constructeur, d'autres de l'appel à the_public_method et d'autres, ajoutés de manière aléatoire à d'autres endroits. À mon avis, c'est un gâchis.
Qu'en est-il du numéro 2? Eh bien, premièrement, examinons le deuxième problème avec celui-ci:
Ils seraient dans un module ensemble, donc ils seraient totalement regroupés.
À mon avis, c'est bien. Cela rend explicites les dépendances de données dans votre algorithme. En les stockant, que ce soit dans des variables globales ou sur un individu, vous masquez les dépendances et les rendez moins pires, mais elles sont toujours là.
Habituellement, lorsque cette situation se présente, cela signifie que vous n’avez pas trouvé le bon moyen de décomposer votre problème. Vous trouvez qu'il est difficile de diviser en plusieurs fonctions parce que vous essayez de le diviser dans le mauvais sens.
Bien sûr, sans voir votre fonction réelle, il est difficile de deviner ce qui serait une bonne suggestion. Mais vous donnez un aperçu de ce dont vous avez affaire ici:
Permettez-moi de choisir un exemple de quelque chose qui correspond en quelque sorte à votre description, un installateur. Un programme d’installation doit copier un grand nombre de fichiers, mais si vous l’annulez à mi-chemin, vous devez revenir en arrière dans l’ensemble du processus, y compris la remise en place des fichiers que vous avez remplacés. L'algorithme pour cela ressemble à quelque chose comme:
Maintenant, multipliez cette logique pour avoir à définir les paramètres du registre, les icônes de programme, etc., et vous aurez un désordre total.
Je pense que votre solution n ° 1 ressemble à:
Cela rend l'algorithme global plus clair, mais cache la façon dont les différentes pièces communiquent.
L'approche n ° 2 ressemble à quelque chose comme:
Maintenant, il est explicite de savoir comment les éléments de données se déplacent entre les fonctions, mais c’est très délicat.
Alors, comment cette fonction devrait-elle être écrite?
L'objet FileTransactionLog est un gestionnaire de contexte. Lorsque copy_new_files copie un fichier, il le fait via le FileTransactionLog qui gère la copie temporaire et garde la trace des fichiers copiés. En cas d'exception, il copie les fichiers d'origine et en cas de succès, il supprime les copies temporaires.
Cela fonctionne car nous avons trouvé une décomposition plus naturelle de la tâche. Auparavant, nous mêlions la logique d'installation de l'application à la logique de récupération d'une installation annulée. Maintenant, le journal des transactions gère tous les détails concernant les fichiers temporaires et la comptabilité, et la fonction peut se concentrer sur l'algorithme de base.
Je soupçonne que votre cas est dans le même bateau. Vous devez extraire les éléments de comptabilité dans une sorte d’objet afin que votre tâche complexe puisse s’exprimer de manière plus simple et élégante.
la source
Étant donné que le seul inconvénient apparent de la méthode 1 est son modèle d'utilisation sous-optimal, je pense que la meilleure solution consiste à conduire l'encapsulation un peu plus loin: Utilisez une classe, mais fournissez également une fonction autonome, qui construit uniquement l'objet, appelle la méthode et renvoie :
Avec cela, vous avez la plus petite interface possible avec votre code, et la classe que vous utilisez en interne devient vraiment un détail d'implémentation de votre fonction publique. Le code d'appel n'a jamais besoin de connaître votre classe interne.
la source
D'une part, la question est en quelque sorte agnostique. mais d'autre part, la mise en œuvre dépend du langage et de ses paradigmes. Dans ce cas, il s’agit de Python, qui prend en charge plusieurs paradigmes.
Outre vos solutions, il est également possible d'effectuer des opérations complètes sans état de manière plus fonctionnelle, par exemple:
Tout se résume à
Mais -Si votre base de code est en POO, cela enfreint la cohérence si soudainement certaines parties sont écrites dans un style (plus) fonctionnel.
la source
Pourquoi concevoir quand je peux coder?
J'offre une vision à contre-courant des réponses que j'ai lues. De ce point de vue, toutes les réponses et franchement même la question elle-même se concentrent sur les mécanismes de codage. Mais c'est un problème de conception.
Oui, comme cela a du sens pour votre conception. Ce peut être une classe en soi ou une partie d'une autre classe ou son comportement peut être réparti entre les classes.
La conception orientée objet concerne la complexité
L'objectif de OO est de créer et de gérer avec succès des systèmes complexes et volumineux en encapsulant le code en termes de système lui-même. "Propre" conception dit que tout est dans une classe.
OO Design gère naturellement la complexité principalement via des classes ciblées qui respectent le principe de responsabilité unique. Ces classes donnent structure et fonctionnalité tout au long du souffle et de la profondeur du système, interagissent et intègrent ces dimensions.
Cela dit, on dit souvent que les fonctions qui pendent du système - la classe d’utilité générale trop omniprésente - constituent une odeur de code suggérant une conception insuffisante. J'ai tendance à être d'accord.
la source
Pourquoi ne pas comparer vos besoins à quelque chose qui existe dans la bibliothèque Python standard, puis voir comment cela est implémenté?
Notez que si vous n'avez pas besoin d'un objet, vous pouvez toujours définir des fonctions dans les fonctions. Avec Python 3, il y a la nouvelle
nonlocal
déclaration qui vous permet de changer les variables dans votre fonction parent.Vous pouvez toujours trouver utile de disposer de simples classes privées dans votre fonction pour implémenter des abstractions et des opérations de rangement.
la source
nonlocal
nulle part dans mes bibliothèques python actuellement installées. Vous pouvez peut-être choisirtextwrap.py
uneTextWrapper
classe, mais aussi unedef wrap(text)
fonction qui crée simplement uneTextWrapper
instance, appelle la.wrap()
méthode dessus et retourne. Utilisez donc une classe, mais ajoutez des fonctions pratiques.