Éviter les pièges orientés objet, migrer à partir de C, qu'est-ce qui a fonctionné pour vous?

12

Je programme dans des langages procéduraux depuis un certain temps maintenant, et ma première réaction à un problème est de commencer à le décomposer en tâches à effectuer plutôt que de considérer les différentes entités (objets) qui existent et leurs relations.

J'ai suivi un cours universitaire en POO et je comprends les principes fondamentaux de l'encapsulation, de l'abstraction des données, du polymorphisme, de la modularité et de l'héritage.

J'ai lu /programming/2688910/learning-to-think-in-the-object-oriented-way et /programming/1157847/learning-object-oriented-thinking , et se penchera sur certains des livres mentionnés dans ces réponses.

Je pense que plusieurs de mes projets de taille moyenne à grande bénéficieront d'une utilisation efficace de la POO, mais en tant que novice, je voudrais éviter les erreurs courantes et chronophages.

D'après vos expériences, quels sont ces écueils et quels sont les moyens raisonnables de les contourner? Si vous pouviez expliquer pourquoi ils sont des pièges et comment votre suggestion est efficace pour résoudre le problème, ce serait apprécié.

Je pense à quelque chose comme "Est-il courant d'avoir un bon nombre de méthodes d'observateur et de modificateur et d'utiliser des variables privées ou existe-t-il des techniques pour les consolider / réduire?"

Je ne suis pas inquiet d'utiliser C ++ comme un langage OO pur, s'il y a de bonnes raisons de mélanger les méthodes. (Réminiscence des raisons d'utiliser les GOTO, quoique avec parcimonie.)

Je vous remercie!

Stephen
la source
2
pas digne d'une réponse complète, mais une réponse importante qui m'a pris beaucoup de temps à accepter (c'est-à-dire que j'ai lu beaucoup de temps à ce sujet mais que je l'ai traitée comme un discours de fanboy): préférez les fonctions gratuites aux fonctions membres. Cela maintient vos cours au minimum, ce qui est une bonne chose.
stijn
@stijn Essentiellement, vous dites que s'il n'a pas besoin d'être dans la classe, ne le mettez pas là. Par exemple, je vois beaucoup de fonctions membres d'utilité qui pourraient facilement être des fonctions libres dans le code que j'ai lu jusqu'à présent.
Stephen
Oui c'est ça. Si vous recherchez «préférez les non-membres non-amis», vous trouverez de nombreuses informations à ce sujet. En fin de compte, cela revient à adhérer au principe de responsabilité unique
stijn

Réponses:

10

Une grande chose que j'ai apprise est de concevoir des classes de l'extérieur. Concevez l'interface avant même de commencer à penser à l'implémentation. Cela rendra la classe beaucoup, beaucoup plus intuitive pour vos utilisateurs (ceux qui utilisent la classe) que d'écrire les algorithmes sous-jacents et de construire la classe, et d'écrire de nouvelles fonctions de membre public au besoin.

Dominic Gurto
la source
7
De plus, nous pouvons «programmer par intention», c'est-à-dire écrire un exemple de code qui utiliserait la nouvelle classe, voir quels types de méthodes et de politiques la rendent confortable à utiliser. Utilisez ensuite ces informations comme référence pour les implémentations.
2
Super en théorie - concevoir des interfaces et vous avez essentiellement terminé. Mais cela ne fonctionne pas aussi simple dans la pratique. La conception et la mise en œuvre de l'interface vont souvent de pair dans des itérations consécutives jusqu'à ce que l'interface finale soit cristallisée. À ce moment-là, vous aurez probablement également la mise en œuvre finale.
Gene Bushuyev
9

Eh bien, le premier est l'écueil d'exposer trop d'informations. La valeur par défaut devrait être privatenon public.

Après cela vient trop de getters / setters. Disons que j'ai un membre de données. Ai-je vraiment besoin de ces données dans une autre classe? Ok, fais un getter. Ai -je vraiment, vraiment besoin de changer ces données pendant la durée de vie de l'objet? Faites ensuite un setter.

La plupart des programmeurs novices ont la valeur par défaut pour créer un getter / setter pour chaque membre de données. Cela encombre les interfaces et constitue souvent de mauvais choix de conception.

orlp
la source
2
oui, il est assez populaire parmi ceux qui ont compris l'encapsulation de manière superficielle pour rendre les données privées et ensuite les encapsuler avec des getters et des setters.
Gene Bushuyev
Java est le seul langage populaire qui nécessite encore des méthodes get / set. Tous les autres langages prennent en charge les propriétés, il n'y a donc pas de différence syntaxique entre un membre de données publiques et une propriété. Même en Java, ce n'est pas un péché d'avoir des membres de données publiques si vous contrôlez également tous les utilisateurs de la classe.
kevin cline
Les membres des données publiques sont plus difficiles à maintenir. Avec les getters / setters (ou propriétés), vous pouvez conserver l'interface tout en changeant la représentation des données internes, sans changer de code extérieur. Dans les langues où les propriétés sont syntaxiquement identiques aux variables membres du point de vue du consommateur, ce point ne tient pas cependant.
tdammers
Jusqu'à présent, je pense que je considère les membres privés "en quelque sorte" comme des variables locales dans une fonction ... le reste du monde n'a pas besoin de savoir quoi que ce soit à leur sujet pour que le fn fonctionne ... et si le fn changements, seule l'interface doit être cohérente. Je n'ai pas tendance à aller avec le getter / setter de spam, donc je suis peut-être sur la bonne voie, en fait en regardant une classe de tampon que j'ai écrite, il n'y a pas du tout de membres de données publiques. Merci!
Stephen
2

Lorsque j'ai traversé ce gouffre, j'ai opté pour l'approche suivante:

0) J'ai commencé lentement, avec des applications procédurales de petite / moyenne taille, et aucun truc essentiel à la mission au travail.

1) une simple cartographie de premier passage sur la façon dont j'écrirais le programme à partir de zéro dans le style OO - le plus important pour moi à ce moment-là, et cela EST subjectif - était de comprendre toutes les classes de base. Mon objectif était d'encapsuler autant que possible dans les classes de base. Méthodes virtuelles pures pour tout ce qui est possible dans les classes de base.

2) Ensuite, l'étape suivante a été de créer les dérivations.

3) l'étape finale était - dans le code de procédure d'origine, séparer les structures de données du code obsever / modifier. Ensuite, utilisez les données masquées et mappez toutes les données communes dans les classes de base, et dans les sous-classes, les données qui n'étaient pas communes à l'ensemble du programme sont entrées. Et le même traitement pour le code observateur / modificateur procédural - toute la logique «utilisée partout» est entrée dans les classes de base. Et la logique de visualisation / modification qui n'agissait que sur un sous-ensemble des données est entrée dans les classes dérivées.

C'est subjectif mais c'est RAPIDE, si vous connaissez bien le code procédural et les structures de données. Un ÉCHEC dans une révision de code, c'est quand un peu de données ou de logique n'apparaît pas dans les classes de base mais est utilisé partout.


la source
2

Jetez un oeil à d'autres projets réussis qui utilisent la POO pour avoir une idée du bon style. Je recommande de regarder Qt , un projet que je regarde toujours lorsque je prends mes propres décisions de conception.

rcv
la source
Oui! Avant qu'une personne ne devienne maître de quelque chose, elle apprend à imiter les grands maîtres qui ont travaillé avant elle. L'étude du bon code mis en œuvre par d'autres est un bon moyen d'apprendre. Je recommanderais de regarder le boost, à partir de conceptions simples et d'approfondir à mesure que la compréhension s'améliore.
Gene Bushuyev
1

Selon la personne à qui vous parlez, toutes les données de la POO doivent être privées ou protégées, et uniquement disponibles via des accesseurs et des mutateurs. En général, je pense que c'est une bonne pratique, mais il y a des occasions où je diverge de cette norme. Par exemple, si vous avez une classe (en Java, disons) dont le seul but est de regrouper certains éléments de données dans une unité logique, il est logique de laisser les champs publics. S'il s'agit d'une capsule immuable, il suffit de les marquer comme finales et de les initialiser dans le constructeur. Cela réduit la classe (dans ce cas) à un peu plus qu'une structure (en fait, en utilisant C ++, vous devez en fait appeler cela une structure. Fonctionne comme une classe, mais la visibilité par défaut est publique et votre intention est plus claire), mais Je pense que vous constaterez que son utilisation est beaucoup plus confortable dans ces cas.

Une chose que vous ne voulez certainement pas faire est d'avoir un champ dont la cohérence doit être vérifiée dans le mutateur et de le laisser public.

jpm
la source
3
Je suggère également que si vous avez de telles classes qui contiennent uniquement des données publiques, vous devez les déclarer comme structs - cela ne fait aucune différence sémantique, mais cela clarifie l'intention et rend leur utilisation plus naturelle, en particulier pour les programmeurs C.
1
Oui, je suis d'accord ici si vous êtes en C ++ (ce que la question a mentionné), mais je fais la plupart de mon travail en Java, et nous n'avons malheureusement pas de structure.
vous devez faire une distinction claire entre les classes agrégées (cas peu fréquent) et les classes avec fonctionnalité (cas général). Ce dernier doit pratiquer l'encapsulation et accéder à ses membres via une interface publique uniquement, aucune donnée ne doit être exposée au public.
Gene Bushuyev
1

Le livre de Kent Beck, Implementation Patterns, est une excellente base pour savoir comment utiliser, et non abuser, les mécanismes orientés objet.

Steven A. Lowe
la source
1

Je ne suis pas inquiet d'utiliser C ++ comme un langage OO pur, s'il y a de bonnes raisons de mélanger les méthodes. (Réminiscence des raisons d'utiliser les GOTO, quoique avec parcimonie.)

Je ne pensais pas vraiment que j'avais beaucoup à offrir la conversation jusqu'à ce que je voie ce morceau. Je dois être en désaccord avec le sentiment. La POO n'est qu'un des paradigmes qui peuvent et doivent être utilisés en C ++. Franchement, à mon avis, ce n'est pas l'une de ses caractéristiques les plus fortes.

Du point de vue OO, je pense que C ++ est en fait un peu insuffisant. L'idée d'avoir des fonctions non virtuelles par exemple est une tique à cet égard. J'ai eu des arguments avec ceux qui ne sont pas d'accord avec moi, mais les membres non virtuels ne correspondent tout simplement pas au paradigme en ce qui me concerne. Le polymorphisme est un composant clé de l'OO et les classes avec des fonctions non virtuelles ne sont pas polymorphes au sens de l'OO. Donc, en tant que langage OO, je pense que C ++ est en fait plutôt faible par rapport à des langages comme Java ou Objective-C.

La programmation générique, d'autre part, C ++ a celui-ci assez bon. J'ai entendu dire qu'il y avait aussi de meilleurs langages pour cela, mais la combinaison d'objets et de fonctions génériques est quelque chose de très puissant et expressif. De plus, il peut être sacrément rapide en temps de programmation ET en temps de traitement. C'est vraiment dans ce domaine que je pense que le C ++ brille, mais il est vrai que cela pourrait être mieux (support du langage pour les concepts par exemple). Quelqu'un pensant qu'ils devraient s'en tenir au paradigme OO et traiter les autres dans l'ordre de la déclaration goto en termes d'immoralité est vraiment absent en ne regardant pas ce paradigme.

La capacité de métaprogrammation des modèles est également assez impressionnante. Consultez la bibliothèque Boost.Units par exemple. Cette bibliothèque fournit un support de type pour les quantités dimensionnelles. J'ai beaucoup utilisé cette bibliothèque dans la firme d'ingénierie pour laquelle je travaille actuellement. Il fournit simplement une rétroaction beaucoup plus immédiate pour un aspect du programmeur possible, ou même une erreur de spécification. Il est impossible de compiler un programme qui utilise une formule où les deux côtés de l'opérateur '=' ne sont pas dimensionnellement équivalents sans transtypage explicite. Personnellement, je n'ai aucune expérience avec un autre langage dans lequel cela est possible, et certainement pas avec un autre qui a également la puissance et la vitesse de C ++.

La métaprogrammation est un paradigme purement fonctionnel.

Donc vraiment, je pense que vous entrez déjà dans le C ++ avec quelques malentendus malheureux. Les autres paradigmes en dehors de l'OO ne sont pas à éviter, ils doivent être LEVERAGÉS. Utilisez le paradigme naturel pour l'aspect du problème sur lequel vous travaillez. Ne forcez pas les objets sur ce qui n'est essentiellement pas un problème sujet. En ce qui me concerne, OO n'est même pas la moitié de l'histoire de C ++.

Edward Strange
la source
Le mot-clé virtuel ne fournit-il pas de fonctionnalité de classe virtuelle en C ++? En ce qui concerne le commentaire de goto, ce que je voulais dire, c'est que je ne suis pas préoccupé par la violation des règles empiriques, tant que je comprends le raisonnement. J'ai cependant cherché plus loin à éviter l'impératif / la procédure en faveur de l'OO qu'il n'aurait pu être nécessaire. Merci.
Stephen
les méthodes virtuelles ne sont pas une condition préalable au polymorphisme, c'est juste une façon courante de le faire. en fait, tout le reste est égal, puis rendre une méthode virtuelle affaiblit l'encapsulation, car vous augmentez la taille de l'api de la classe et vous compliquez la tâche de vous assurer de suivre liskov et ainsi de suite. faire quelque chose de virtuel uniquement si vous avez besoin d'une couture, un endroit pour injecter un nouveau comportement via l'héritage (bien que l'héritage soit également quelque chose à faire attention dans la POO). virtual for virtual's sake ne fait pas une classe "plus OOP"
sara
1

Je voulais accepter une réponse à cette question, mais je ne pouvais pas décider d'une réponse à laquelle accorder la coche. En tant que tel, j'ai surévalué les auteurs originaux et créé cela comme une réponse sommaire. Merci à tous ceux qui ont pris quelques minutes, j'ai trouvé que la perspicacité que vous m'avez fournie m'a donné une bonne direction et un peu d'assurance que je n'étais pas déraillé.

@nightcracker

Eh bien, le premier est l'écueil d'exposer trop d'informations. La valeur par défaut doit être privée et non publique. Après cela vient trop de getters / setters.

J'ai senti que j'avais observé ce problème en action dans le passé. Vos commentaires m'ont également rappelé qu'en masquant les variables sous-jacentes et leur implémentation, je suis libre de modifier leur implémentation sans détruire quoi que ce soit qui en dépende.

Dominic Gurto

Concevez l'interface avant même de commencer à penser à la mise en œuvre. La conception et la mise en œuvre de l'interface Gene Bushuyev vont souvent de pair dans des itérations consécutives jusqu'à ce que l'interface finale soit cristallisée.

Je pensais que le commentaire de Dominic était un grand idéal auquel aspirer, mais je pense que le commentaire de Gene frappe vraiment la réalité de la situation. Jusqu'à présent, j'ai vu cela en action ... et je me sens un peu mieux que ce n'est pas rare. Je pense qu'à mesure que je serai mature en tant que programmeur, je m'orienterai vers des conceptions plus complètes, mais en ce moment je souffre toujours de sauter dedans et d'obtenir du code écrit.

wantTheBest

J'ai commencé lentement, avec des applications procédurales de petite / moyenne taille, et aucun truc essentiel à la mission au travail. dans le code procédural d'origine, séparez les structures de données du code obsever / modifier

Cela a beaucoup de sens ... J'ai aimé l'idée de garder les choses au travail, mais de remanier certaines des choses non critiques avec les classes.

jpm

Une chose que vous ne voulez certainement pas faire est d'avoir un champ dont la cohérence dans le mutateur doit être vérifiée et le laisser public

Je sais depuis un certain temps que c'est l'une des forces de l'encapsulation des données ... être en mesure d'imposer la cohérence et, par ailleurs, les conditions / plages / etc.

Crazy Eddie

Quelqu'un pensant qu'ils devraient s'en tenir au paradigme OO et traiter les autres dans l'ordre de la déclaration goto en termes d'immoralité est vraiment absent en ne regardant pas ce paradigme. La capacité de métaprogrammation des modèles est également assez impressionnante.

À l'origine, j'ai manqué beaucoup de choses dans la réponse de Crazy Eddie, je pense parce que je n'avais pas lu certains des sujets mentionnés ... comme la métaprogrammation. Je pense que le grand message dans le post de CE était que C ++ est un tel mélange de capacités et de styles que chacun devrait être utilisé à son meilleur potentiel ... y compris impératif si c'est ce qui a du sens.

Encore une fois, merci à tous ceux qui ont répondu!

Stephen
la source
0

Le plus grand écueil est la croyance que la POO est une solution miracle, ou le «paradigme parfait».

alternative
la source