Je suis en train de concevoir mon application et je ne suis pas sûr de comprendre SOLID et OOP correctement. Les classes doivent faire 1 chose et bien le faire mais de l'autre) ils devraient représenter des objets réels avec lesquels nous travaillons.
Dans mon cas, je fais une extraction d'entités sur un ensemble de données, puis je fais une analyse d'apprentissage automatique. Je suppose que je pourrais créer trois classes
Mais la classe FeatureExtractor ne représente rien, elle fait quelque chose qui en fait plus une routine qu'une classe. Il n'aura qu'une seule fonction qui 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 () ressemblait à ça: vaut-il la peine de créer une classe spéciale pour contenir 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
Les cours devraient faire 1 chose et bien le faire
Oui, c'est généralement une bonne approche.
mais d'un autre côté, ils devraient représenter un objet réel avec lequel nous travaillons.
Non, c'est un malentendu commun à mon humble avis. Un bon accès débutant à OOP est souvent "commencer avec des objets représentant des choses du monde réel" , c'est vrai .
Cependant, vous ne devez pas vous arrêter là-dessus!
Les classes peuvent (et doivent) être utilisées pour structurer votre programme de différentes manières. La modélisation d'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 seule méthode publicextract_features()
, je serais étonné que s'il ne contienne pas non plus beaucoup de méthodes privées et peut-être un état partagé. Donc, avoir une classe FeatureExtractor
introduira un emplacement naturel pour ces méthodes privées.
Note latérale: dans des langages comme Python qui supportent un concept de module séparé, on peut également utiliser un module FeatureExtractor
pour cela, mais dans le contexte de cette question, c'est à mon humble avis une différence négligeable.
De plus, un "extracteur de fonctionnalités" peut être imaginé comme "une personne ou un bot qui extrait des fonctionnalités". C'est une "chose" abstraite, peut-être pas une 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 la responsabilité de cette classe. Je ne suis donc pas d'accord que cette classe ne "représente rien".
Doc Brown est parfait: 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 supplémentaires, et à quoi correspondent int
ou string
dans le monde réel? Ce sont des descriptions abstraites, pas des choses concrètes et tangibles.
Cela dit, votre cas est spécial. Selon votre description:
Et si extract_features () ressemblait à ça: vaut-il la peine de créer une classe spéciale pour contenir cette méthode?
Vous avez tout à fait raison: si votre code est comme indiqué, il ne sert à rien d'en faire une classe. Il y a n discours célèbre qui fait valoir que de telles utilisations de classes dans Python sont une odeur de code, et que des fonctions simples sont souvent suffisantes. Votre cas en est un parfait exemple.
La surutilisation des classes est due au fait que OOP est devenu courant avec Java dans les années 1990. Malheureusement Java à l'époque) il manquait plusieurs fonctionnalités du langage moderne (telles que les fermetures), ce qui signifie que de nombreux concepts étaient difficiles ou impossibles à exprimer sans l'utilisation de classes. Par exemple, il était impossible en Java jusqu'à récemment d'avoir des méthodes qui portait l'état (c'est-à-dire les fermetures). Au lieu de cela, vous deviez écrire une classe pour porter l'état, et qui exposait 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 à cause de n livre de génie logiciel influent qui est par ailleurs très utile), même dans les langages qui ne nécessitent pas une telle solutions de contournement.
En Python, les classes sont évidemment un outil très important et devraient être utilisées généreusement. Mais ce n'est pas seulement l'outil , et il n'y a aucune raison de les utiliser là où cela n'a pas de sens. C'est une idée fausse que les fonctions libres n'ont pas leur place dans la POO.
Je ne fais que concevoir mon application et je ne sais pas si je comprends bien SOLID et OOP correctement.
Cela fait plus de 20 ans et je ne suis pas sûr non plus.
Les cours devraient faire 1 chose et bien le faire
Difficile de se tromper ici.
ils doivent représenter des objets réels avec lesquels nous travaillons.
Oh vraiment? Laissez-moi vous présenter la classe la plus populaire et la plus réussie 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 ce que l'on appelle une métaphore. C'est OK 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 n'ont pas besoin d'être réelles. Je viens de comprendre facilement.
Une bonne OOP conception regroupe les messages (méthodes) autour des données (état) afin que les réactions à ces messages puissent varier en fonction de ces données. Si cela modélise quelque chose du monde réel, spiffy. Sinon , eh bien. Tant que cela a du sens pour le lecteur, ça va.
Maintenant, bien sûr, vous pourriez y penser comme ceci:
mais si vous pensez que cela doit exister dans le monde réel avant de pouvoir utiliser la métaphore, eh bien votre carrière de programmeur va impliquer beaucoup d'art et d'artisanat.
Il faut se méfier! Nulle part SOLID dit qu'une classe ne devrait "faire qu'une seule chose". Si tel é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 ne seule responsabilité. Ce sont un peu comme les responsabilités des personnes dans 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 point de cela est - s'il y a un changement dans les exigences, vous ne devez idéalement modifier qu'une seule classe. Cela rend le code plus facile à comprendre, à modifier et à réduire les risques.
Il n'y a pas de règle selon laquelle un objet doit représenter "une chose réelle". Ceci est juste une tradition culte du fret puisque OO était initialement inventé pour être utilisé dans les simulations. Mais votre programme n'est pas une simulation (peu moderne OO applications is), donc cette règle ne s'applique pas. Tant que chaque classe a une responsabilité bien définie, ça devrait aller.
Si une classe n'a vraiment qu'une seule méthode et la classe n'a pas d'état, vous pouvez envisager d'en faire une fonction autonome. C'est très bien et suit les principes KISS et YAGNI - pas besoin de créer une classe si vous pouvez la résoudre avec une fonction. Par contre, si vous avez des raisons de croire que vous pourriez avoir besoin état interne ou implémentations multiples, vous pourriez aussi bien en faire une classe dès le départ. Vous devrez utiliser votre meilleur jugement ici.
Est-il correct de créer des classes qui ne représentent pas une chose mais font une chose?
En général, c'est OK.
Sans une description un peu plus précise de ce que la classe FeatureExtractor
est censée faire exactement, il est difficile de dire.
Quoi qu'il en soit, même si la FeatureExtractor
expose uniquement une fonction publique extract_features()
, je pourrais penser à la configurer avec une classe Strategy
, qui détermine comment exactement l'extraction doit être effectuée.
Un autre exemple est une classe avec fonction de modèle .
Et il y a plus Patterns de conception comportementale , qui sont basés sur des modèles de classe.
Comme vous avez ajouté du code pour des éclaircissements.
Et si extract_features () ressemblait à ça: vaut-il la peine de créer une classe spéciale pour contenir cette méthode?
La ligne
sent = SentimentAnalyser()
comprend exactement ce que je voulais dire que vous pourriez configurer une classe avec une stratégie .
Si vous avez une interface pour cette classe SentimentAnalyser
, vous pouvez la transmettre à la classe FeatureExtractor
à son point de construction, au lieu de la coupler directement à cette implémentation spécifique dans votre fonction.
Mis à part les modèles et tous les langages/concepts fantaisistes: ce que vous avez trouvé est un Job ou un Batch Process.
À la fin de la journée, même un programme OOP pur doit être en quelque sorte piloté par quelque chose, pour réellement effectuer le travail; il doit y avoir un point d'entrée d'une manière ou d'une autre. 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.
Est-il correct de créer des classes qui ne représentent pas une chose mais font une chose?
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 vient à l'esprit.
Et si extract_features () ressemblait à ça: vaut-il la peine de créer une classe spéciale pour contenir cette méthode?
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 de ligne de commande unique, alors une méthode au lieu d'une classe devrait convenir. La première version de votre programme peut s'en tirer avec une méthode, c'est sûr. Si, plus tard, vous constatez que vous vous retrouvez avec des dizaines de ces méthodes, peut-être même avec des variables globales mélangées, il est temps de refactoriser les classes.
Nous pouvons penser à OOP comme modélisation le comportement d'un système. Notez que le système pas doit exister dans le 'réel monde ", bien que les métaphores du monde réel puissent parfois être utiles (par exemple" pipelines "," usines ", etc.).
Si notre système souhaité est trop compliqué pour être modélisé en une seule fois, nous pouvons le décomposer en morceaux plus petits et modéliser ceux (le "domaine problématique"), ce qui peut impliquer une décomposition plus poussée, et ainsi de suite jusqu'à ce que nous obtenions des pièces dont le comportement correspond (plus ou moins) celle d'un objet langage intégré comme un nombre, une chaîne, une liste, etc.
Une fois que nous avons ces pièces simples, nous pouvons les combiner ensemble pour décrire le comportement de pièces plus grandes, que nous pouvons combiner ensemble en pièces encore plus grandes, et ainsi de suite jusqu'à ce que nous puissions décrire tous les composants du domaine qui sont nécessaires pour un ensemble système.
C'est cette phase de "combinaison" où nous pourrions écrire quelques 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 bars appelées "bazs". Nous pouvons remarquer que les foos sont assez simples à modéliser avec des chaînes, nous le faisons donc. Nous constatons que les barres nécessitent que leur contenu obéisse à une contrainte particulière qui ne correspond à rien 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 , afin que nous puissions 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 pour s'il y a déjà quelque chose avec le bon comportement. En particulier, pour qu'une classe soit utile, elle doit "fournir" quelque chose (données, méthodes, constantes, sous-classes, etc.), donc même si nous avons plusieurs couches de classes personnalisées, nous devons éventuellement utiliser une fonction intégrée; par exemple, si nous écrivions une nouvelle classe pour foos, elle ne contiendrait probablement qu'une chaîne, alors pourquoi ne pas oublier la classe foo et demander à la classe bar de contenir ces chaînes à la place? Gardez à l'esprit que les classes sont également un objet intégré, elles sont juste particulièrement flexibles.
Une fois que nous avons notre modèle de domaine, nous pouvons prendre des instances particuliers de ces pièces et les organiser en une "simulation" du système particulier que nous voulons modéliser (par exemple "un système d'apprentissage automatique pour .. . ").
Une fois que nous avons cette simulation, nous pouvons l'exécuter et hé hop, nous avons un système d'apprentissage automatique (simulation d'un) pour ... (ou tout ce que nous modélisons).
Maintenant, dans votre situation particulière, vous essayez de modéliser le comportement d'un composant "extracteur de fonctionnalités". La question est, y a-t-il des objets intégrés qui se comportent comme un "extracteur de fonctionnalités", ou devrez-vous le décomposer en choses plus simples? Il semble que les extracteurs de fonctionnalités se comportent très bien comme des objets de fonction, donc je pense que vous seriez bien de les utiliser comme modèle.
Une chose à garder à l'esprit lors de l'apprentissage de ces types de concepts est que différents langages peuvent fournir différentes fonctionnalités et objets intégrés (et, bien sûr, certains n'utilisent même pas une terminologie comme "objets"!). Par conséquent, les 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, un lot de la littérature OOP (en particulier "design patterns") s'est concentré sur Java, qui est assez différent de Python. Par exemple, Java ne sont pas des objets, Java n'avait pas d'objets fonction jusqu'à très récemment, Java a une vérification de type stricte (qui encourage les interfaces et sous-classement) tandis que Python encourage le typage du canard, Java n'a pas d'objets de module, Java nombres entiers/flottants/etc) ne sont pas des objets, méta-programmation/introspection en Java nécessite une "réflexion", etc.).
Je n'essaie pas de choisir Java (comme un autre exemple, beaucoup de OOP 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 attentivement au contexte et aux contraintes dans lesquelles les solutions ont été élaborées, et si cela correspond à la situation dans laquelle nous nous trouvons.
Dans votre cas, un objet fonction semble être un bon choix. Si vous vous demandez pourquoi certaines lignes directrices sur les "meilleures pratiques" ne mentionnent pas les objets fonction comme une solution possible, c'est peut-être simplement parce que ces lignes directrices ont été écrites pour les anciennes versions de Java!
De façon pragmatique, quand j'ai une "chose diverse qui fait quelque chose d'important et qui devrait être séparé", et qu'elle n'a pas de maison claire, je la mets dans une section Utilities
et je l'utilise comme convention de nommage. c'est à dire. FeatureExtractionUtility
.
Oubliez le nombre de méthodes dans une classe; une seule méthode aujourd'hui devra peut-être passer à cinq méthodes demain. Ce qui importe, c'est une structure organisationnelle claire et cohérente, comme un espace utilitaires pour diverses collections de fonctions.