Je suis en train de concevoir mon application et je ne suis pas sûr de bien comprendre SOLID et OOP. Les classes doivent faire une chose et le faire bien, mais de l’autre côté, elles doivent représenter de vrais objets avec lesquels nous travaillons.
Dans mon cas, je procède à une extraction de caractéristiques sur un jeu de données, puis à une analyse par apprentissage automatique. Je suppose que je pourrais créer trois classes
- FeatureExtractor
- DataSet
- Analyseur
Mais la classe FeatureExtractor ne représente rien, elle fait quelque chose qui la rend plus une routine qu'une classe. Une seule fonction sera utilisée: extract_features ()
Est-il correct de créer des classes qui ne représentent pas une chose mais font une chose?
EDIT: je ne sais pas si c'est important, mais j'utilise Python
Et si extract_features () ressemblerait à cela: vaut-il la peine de créer une classe spéciale pour cette méthode?
def extract_features(df):
extr = PhrasesExtractor()
extr.build_vocabulary(df["Text"].tolist())
sent = SentimentAnalyser()
sent.load()
df = add_features(df, extr.features)
df = mark_features(df, extr.extract_features)
df = drop_infrequent_features(df)
df = another_processing1(df)
df = another_processing2(df)
df = another_processing3(df)
df = set_sentiment(df, sent.get_sentiment)
return df
la source
Réponses:
Oui, c'est généralement une bonne approche.
Non, c'est un malentendu commun à mon humble avis. L'accès à la programmation orientée objet pour un bon débutant est souvent "commencer par des objets représentant des choses du monde réel" , c'est vrai.
Cependant, vous ne devriez pas vous arrêter avec ça !
Les classes peuvent (et devraient) être utilisées pour structurer votre programme de différentes manières. Modéliser des objets du monde réel en est un aspect, mais pas le seul. La création de modules ou de composants pour une tâche spécifique est un autre cas d'utilisation judicieux pour les classes. Un "extracteur de fonctionnalités" est probablement un tel module, et même s'il ne contient qu'une méthode publique
extract_features()
, je serais étonné si ne contenait pas aussi beaucoup de méthodes privées et peut-être un état partagé. Donc, avoir une classeFeatureExtractor
introduira un emplacement naturel pour ces méthodes privées.Note secondaire: dans des langages tels que Python qui prennent en charge un concept de module distinct, on peut également utiliser un module
FeatureExtractor
pour cela, mais dans le contexte de cette question, il s'agit d'une différence négligeable à mon humble avis.De plus, un "extracteur de fonctionnalités" peut être imaginé comme "une personne ou un robot qui extrait des fonctionnalités". C'est une "chose" abstraite, peut-être pas quelque chose que vous trouverez dans le monde réel, mais le nom lui-même est une abstraction utile, qui donne à chacun une idée de ce que la responsabilité de cette classe est. Je ne suis donc pas d'accord pour dire que cette classe ne "représente rien".
la source
extract_features
? J'ai en quelque sorte simplement supposé qu'il s'agissait de fonctions publiques d'un autre endroit. Très bien, je conviens que si elles sont privées, elles devraient probablement entrer dans un module (mais quand même: pas dans une classe, à moins de partager l’état), avecextract_features
. (Cela dit, vous pouvez bien sûr les déclarer localement dans cette fonction.)Doc Brown est sur place: les classes n'ont pas besoin de représenter des objets du monde réel. Ils ont juste besoin d'être utiles . Les classes ne sont fondamentalement que des types additionnels, et à quoi correspond
int
oustring
correspond le monde réel? Ce sont des descriptions abstraites et non des choses concrètes et tangibles.Cela dit, votre cas est spécial. Selon votre description:
Vous avez absolument raison: si votre code est tel qu’il est montré, il ne sert à rien de le transformer en classe. Il y a un discours célèbre qui fait valoir que ces utilisations des classes en Python sont odeur de code, et que les fonctions simples sont souvent suffisantes. Votre cas en est un exemple parfait.
La surutilisation des classes est due au fait que la programmation orientée objet est devenue un courant dominant avec Java dans les années 1990. Malheureusement, Java ne disposait pas à l’époque de nombreuses fonctionnalités du langage moderne (telles que les fermetures), ce qui signifie que de nombreux concepts étaient difficiles, voire impossibles à exprimer, sans l’utilisation de classes. Par exemple, il était impossible jusqu'à récemment en Java de disposer de méthodes comportant un état (c'est-à-dire des fermetures). Au lieu de cela, vous deviez écrire une classe pour porter l’état et exposer une seule méthode (appelée quelque chose comme
invoke
).Malheureusement, ce style de programmation est devenu populaire bien au-delà de Java (en partie grâce à un ouvrage de génie logiciel influent par ailleurs très utile), même dans des langages ne requérant pas de telles solutions de contournement.
En Python, les classes sont évidemment un outil très important et doivent être utilisées généreusement. Mais ils ne sont pas le seul outil et il n’ya aucune raison de les utiliser là où ils n’ont pas de sens. C'est une idée fausse commune que les fonctions libres n'ont pas de place dans la POO.
la source
__call__
définition, dans ce cas, il suffit d'utiliser une fonction!)__call__()
? Est-ce vraiment si différent d'une instance de classe interne anonyme? Syntaxiquement, bien sûr, mais du point de vue du langage, cela semble être une distinction moins importante que celle que vous présentez ici.Ça fait 20 ans que je suis là et je ne suis pas sûr non plus.
Difficile de se tromper ici.
Oh vraiment? Permettez - moi de vous présenter à la classe la plus populaire et de tous les temps:
String
. Nous l'utilisons pour le texte. Et l'objet du monde réel qu'il représente est le suivant:Pourquoi non, tous les programmeurs ne sont pas obsédés par la pêche. Ici, nous utilisons quelque chose appelé une métaphore. C'est bien de faire des modèles de choses qui n'existent pas vraiment. C'est l'idée qui doit être claire. Vous créez des images dans l'esprit de vos lecteurs. Ces images ne doivent pas nécessairement être réelles. Je viens de comprendre facilement.
Une bonne conception POO regroupe les messages (méthodes) autour des données (état) de sorte que les réactions à ces messages puissent varier en fonction de ces données. Si cela vous donne une idée du monde réel, spiffy. Si non, eh bien. Tant que cela a du sens pour le lecteur, ça va.
Maintenant, bien sûr, vous pourriez penser à ça comme ça:
mais si vous pensez que cela doit exister dans le monde réel avant de pouvoir utiliser la métaphore, votre carrière en programmation impliquera de nombreuses activités artistiques et artisanales.
la source
class
ettable
etcolumn
...Il faut se méfier! SOLID ne dit nulle part qu'une classe ne devrait «faire qu'une chose». Si c'était le cas, les classes n'auraient jamais qu'une seule méthode et il n'y aurait pas vraiment de différence entre les classes et les fonctions.
SOLID dit qu'une classe devrait représenter une responsabilité unique . Celles-ci ressemblent un peu aux responsabilités des membres d’une équipe: le conducteur, l’avocat, le pickpocket, le graphiste, etc. Chacune de ces personnes peut effectuer plusieurs tâches (liées), mais toutes relevant d’une seule responsabilité.
Le but de ceci est - si les exigences changent, vous ne devez idéalement que modifier une seule classe. Cela rend le code plus facile à comprendre, à modifier et à réduire les risques.
Il n’existe aucune règle selon laquelle un objet doit représenter "une chose réelle". Il s’agit simplement d’un savoir-faire culte du fret depuis que OO a été initialement inventé pour être utilisé dans des simulations. Mais votre programme n'est pas une simulation (peu d'applications OO modernes le sont), cette règle ne s'applique donc pas. Tant que chaque classe a une responsabilité bien définie, ça devrait aller.
Si une classe n'a en réalité qu'une seule méthode et qu'elle n'a aucun état, vous pouvez envisager de la transformer en une fonction autonome. C’est certes correct et respecte les principes KISS et YAGNI - inutile de faire un cours si vous pouvez le résoudre avec une fonction. D'un autre côté, si vous avez des raisons de croire que vous pourriez avoir besoin d'un état interne ou de plusieurs implémentations, vous pouvez également en faire une classe à l'avance. Vous devrez utiliser votre meilleur jugement ici.
la source
En général c'est OK.
Sans une description un peu plus précise,
FeatureExtractor
il est difficile de dire ce que la classe est censée faire.Quoi qu’il en soit, même si cela
FeatureExtractor
n’expose qu’uneextract_features()
fonction publique , je pourrais penser à la configurer avec uneStrategy
classe, qui détermine exactement comment l’extraction doit être effectuée.Un autre exemple est une classe avec une fonction Template .
Et il y a plus de modèles de conception comportementaux , basés sur des modèles de classe.
Comme vous avez ajouté du code pour des éclaircissements.
La ligne
comprend exactement ce que je voulais dire que vous pouvez configurer une classe avec une stratégie .
Si vous avez une interface pour cette
SentimentAnalyser
classe, vous pouvez la transmettre à laFeatureExtractor
classe au moment de sa construction, au lieu de la coupler directement à cette implémentation spécifique de votre fonction.la source
FeatureExtractor
classe), mais d'introduire encore plus de complexité (une interface pour laSentimentAnalyser
classe). Si le découplage est souhaitable, alors la fonctionextract_features
peut être considéréeget_sentiment
comme un argument (l'load
appel semble indépendant de la fonction et n'est appelé que pour ses effets). Notez également que Python n'a pas / encourage les interfaces.CacheingFeatureExtractor
ou aTimeSeriesDependentFeatureExtractor
), un objet conviendrait mieux. Tout simplement parce qu'il n'y a pas besoin d'un objet actuellement ne signifie pas sera jamais là.__call__
La méthode sera compatible si vous en avez besoin (vous ne le ferez pas), quatrièmement en ajoutant un wrapper commeFeatureExtractor
si vous rendiez le code incompatible avec tout autre code jamais écrit (à moins que vous ne fournissiez une__call__
méthode, dans ce cas, une fonction serait clairement plus simple )Motifs et tous les langages / concepts sophistiqués mis à part: ce que vous avez découvert est un travail ou un traitement par lots .
En fin de compte, même un programme purement POO doit en quelque sorte être guidé par quelque chose, pour réellement effectuer un travail; il doit y avoir un point d'entrée en quelque sorte. Dans le modèle MVC, par exemple, le contrôleur "C" reçoit les événements de clic, etc. de l'interface graphique, puis orchestre les autres composants. Dans les outils de ligne de commande classiques, une fonction "principale" ferait de même.
Votre classe représente une entité qui fait quelque chose et orchestre tout le reste. Vous pouvez le nommer Controller , Job , Main ou tout ce qui vous passe par la tête.
Cela dépend des circonstances (et je ne connais pas la manière habituelle de procéder en Python). S'il ne s'agit que d'un petit outil en ligne de commande unique, une méthode plutôt qu'une classe devrait convenir. La première version de votre programme peut s’en tirer avec une méthode, bien sûr. Si, par la suite, vous vous retrouvez avec des dizaines de méthodes de ce type, peut-être même avec des variables globales mélangées, il est temps de refactoriser les classes.
la source
this
ou expliciteself
) de la méthode. Les méthodes d'un objet étant mutuellement "ouvertement" récursives, le remplacementfoo
entraîne donc que tous lesself.foo
appels utilisent ce remplacement.Nous pouvons penser à la programmation orientée objet en tant que modélisation du comportement d'un système. Notez que le système ne doit pas nécessairement exister dans le "monde réel", bien que des métaphores du monde réel puissent parfois être utiles (par exemple, "pipelines", "usines", etc.).
Si notre système souhaité est trop compliqué à modéliser en une fois, nous pouvons le décomposer en éléments plus petits et les modéliser (le "domaine du problème"), ce qui peut impliquer une décomposition plus poussée, et ainsi de suite jusqu'à ce que nous obtenions des comportements dont le comportement correspond (plus ou moins) celui d'un objet langage intégré tel qu'un nombre, une chaîne de caractères, une liste, etc.
Une fois que nous avons ces pièces simples, nous pouvons les combiner pour décrire le comportement de pièces plus grandes, que nous pouvons combiner en pièces encore plus grandes, et ainsi de suite jusqu'à ce que nous puissions décrire toutes les composantes du domaine nécessaires à l'ensemble. système.
C'est cette phase de "combinaison" dans laquelle nous pourrions écrire certaines classes. Nous écrivons des classes quand il n'y a pas d'objet existant qui se comporte comme nous le voulons. Par exemple, notre domaine peut contenir des "foos", des collections de foos appelées "bars" et des collections de barres appelées "bazs". Nous pouvons remarquer que les foos sont assez simples pour modéliser avec des chaînes, alors nous le faisons. Nous constatons que les barres exigent que leur contenu obéisse à une contrainte particulière qui ne correspond à rien de ce que Python fournit, auquel cas nous pourrions écrire une nouvelle classe pour appliquer cette contrainte. Peut-être que les bazs n'ont pas de telles particularités, alors nous pouvons simplement les représenter avec une liste.
Notez que nous pourrions écrire une nouvelle classe pour chacun de ces composants (foos, bars et bazs), mais nous n’avons pas besoin de le faire s’il existe déjà un comportement correct. En particulier, pour une classe soit utile , il doit «fournir quelque chose (données, les méthodes, les constantes, les sous - classes, etc.), même si nous avons beaucoup de couches de classes personnalisées , nous devons éventuellement utiliser une fonctionnalité intégrée; Par exemple, si nous écrivions une nouvelle classe pour les foos, elle ne contiendrait probablement qu'une chaîne, alors pourquoi ne pas oublier la classe foo et laisser la classe bar contenir ces chaînes à la place? Gardez à l'esprit que les classes sont aussi un objet intégré, elles sont simplement particulièrement flexibles.
Une fois que nous avons notre modèle de domaine, nous pouvons prendre certaines instances particulières de ces éléments et les organiser dans une "simulation" du système particulier que nous souhaitons modéliser (par exemple, "un système d'apprentissage automatique pour ...").
Une fois que nous avons cette simulation, nous pouvons la lancer et hop, nous avons un système de travail (simulation d’un) apprentissage automatique pour ... (ou tout ce que nous modélisons).
Dans votre cas particulier, vous essayez de modéliser le comportement d'un composant "extracteur de fonctionnalités". La question est la suivante: existe-t-il des objets intégrés se comportant comme un "extracteur de fonctionnalités", ou devrez-vous les séparer en éléments plus simples? Il semble que les extracteurs de caractéristiques se comportent beaucoup comme des objets fonction. Je pense donc que vous seriez prêt à les utiliser comme modèle.
Une chose à garder à l'esprit lorsque vous étudiez ces types de concepts est que différentes langues peuvent fournir différentes fonctionnalités et objets intégrés (et, bien sûr, certains n'utilisent même pas une terminologie telle que "objets"!). Par conséquent, des solutions qui ont du sens dans une langue pourraient être moins utiles dans une autre (cela peut même s'appliquer à différentes versions de la même langue!).
Historiquement, une grande partie de la littérature de la programmation orientée objet (en particulier des "modèles de conception") s'est concentrée sur Java, qui est assez différent de Python. Par exemple, les classes Java ne sont pas des objets, Java n’avait pas d’objets fonction jusqu’à très récemment, Java applique une vérification de type stricte (ce qui encourage les interfaces et les sous-classes) tandis que Python encourage le typage, les Java, n’a pas d’objets modules, les entiers Java / flotteurs / etc. les objets ne sont pas des objets, la méta-programmation / introspection en Java nécessite une "réflexion", etc.
Je n'essaie pas de parler de Java (un autre exemple, beaucoup de théorie de la programmation orientée objet tourne autour de Smalltalk, qui est encore très différent de Python), j'essaie simplement de souligner que nous devons réfléchir très soigneusement au contexte et contraintes dans lesquelles des solutions ont été développées, et si cela correspond à la situation dans laquelle nous nous trouvons.
Dans votre cas, un objet de fonction semble être un bon choix. Si vous vous demandez pourquoi certaines directives de "bonne pratique" ne mentionnent pas les objets fonction comme solution possible, c'est peut-être tout simplement parce que ces instructions ont été écrites pour les anciennes versions de Java!
la source
En termes pragmatiques, quand j’ai une "chose diverse qui fait quelque chose d’important et qui devrait être séparée", et qui n’a pas de foyer clair, je le mets dans une
Utilities
section et l’utilise comme convention de nommage. c'est à dire.FeatureExtractionUtility
.Oubliez le nombre de méthodes dans une classe; aujourd'hui, une seule méthode peut nécessiter cinq méthodes demain. Ce qui compte, c'est une structure organisationnelle claire et cohérente, telle qu'un espace utilitaires pour diverses collections de fonctions.
la source