L'idée de base derrière la POO est que les données et le comportement (sur ces données) sont inséparables et qu'ils sont couplés à l'idée d'un objet d'une classe. Les objets ont des données et des méthodes qui fonctionnent avec ça (et d’autres données). Évidemment, selon les principes de la POO, les objets qui ne sont que des données (comme les structures C) sont considérés comme des anti-modèles.
Jusqu'ici tout va bien.
Le problème est que j'ai remarqué que mon code semble aller de plus en plus dans la direction de cet anti-pattern ces derniers temps. Il me semble que plus j'essaye de cacher des informations entre des classes et des conceptions faiblement couplées, plus mes classes deviennent un mélange de classes de données pures sans comportement et de tout comportement sans classes de données.
Je conçois généralement les classes de manière à minimiser leur connaissance de l'existence des autres classes et leur connaissance des interfaces des autres classes. J'applique cela de manière descendante, les classes de niveau inférieur ne connaissent pas les classes de niveau supérieur. Par exemple:
Supposons que vous ayez une API de jeu de cartes générale. Vous avez une classe Card
. Maintenant, cette Card
classe doit déterminer la visibilité pour les joueurs.
Une façon est d'avoir boolean isVisible(Player p)
sur la Card
classe.
Une autre est d'avoir boolean isVisible(Card c)
sur la Player
classe.
Je n'aime pas particulièrement la première approche, qui accorde des connaissances sur les Player
classes supérieures à une Card
classe inférieure .
Au lieu de cela, j'ai opté pour la troisième option où nous avons une Viewport
classe qui, à partir d' Player
une liste de cartes, détermine quelles cartes sont visibles.
Cependant, cette approche prive les classes Card
et les Player
classes d'une fonction membre possible. Une fois que vous faites cela pour d' autres choses que la visibilité des cartes, il vous reste Card
et les Player
classes qui contiennent uniquement des données que toutes les fonctionnalités sont mis en œuvre dans d' autres classes, qui sont pour la plupart des classes sans données, seulement des méthodes, comme le Viewport
ci - dessus.
Cela va clairement à l’encontre de l’idée principale de la programmation orientée objet.
Quelle est la bonne façon? Comment faire pour minimiser les interdépendances entre les classes et les connaissances et couplages supposés, sans pour autant aboutir à une conception bizarre où toutes les classes de bas niveau contiennent uniquement des données et les classes de haut niveau contiennent toutes les méthodes? Quelqu'un at-il une troisième solution ou perspective sur la conception de classe qui évite tout le problème?
PS Voici un autre exemple:
Supposons que vous ayez une classe DocumentId
immuable, qu’un seul BigDecimal id
membre et un getter pour ce membre. Maintenant, vous devez avoir une méthode quelque part, qui donne un DocumentId
retour Document
pour cet identifiant depuis une base de données.
Le faites vous:
- Ajoutez une
Document getDocument(SqlSession)
méthode à laDocumentId
classe, introduisant soudainement des connaissances sur votre persistance ("we're using a database and this query is used to retrieve document by id"
), l’API utilisée pour accéder à une base de données, etc. De plus, cette classe nécessite maintenant la persistance du fichier JAR pour la compilation. - Ajoutez une autre classe avec la méthode
Document getDocument(DocumentId id)
, en laissant laDocumentId
classe morte, sans comportement, comme une classe semblable à la structure.
la source
Réponses:
Ce que vous décrivez est connu sous le nom de modèle de domaine anémique . Comme dans de nombreux principes de conception OOP (comme la loi de Demeter, etc.), il ne vaut pas la peine de se mettre en quatre pour satisfaire une règle.
Il n'y a rien de mal à avoir des sacs de valeurs, tant qu'ils n'encombrent pas tout le paysage et ne comptent pas sur d'autres objets pour faire le ménage qu'ils pourraient faire eux-mêmes .
Ce serait certainement une odeur de code si vous aviez une classe séparée juste pour modifier les propriétés de
Card
- si on pouvait raisonnablement s’attendre à ce qu’elles s’occupent d’elles seules.Mais est-ce vraiment un travail de
Card
savoir à quoiPlayer
il est visible?Et pourquoi mettre en œuvre
Card.isVisibleTo(Player p)
, mais pasPlayer.isVisibleTo(Card c)
? Ou vice versa?Oui, vous pouvez essayer de définir une règle pour cela comme vous l'avez fait - comme si vous étiez
Player
plus élevé qu'unCard
(?) - - mais ce n'est pas si simple à deviner et je devrai regarder plus d'une place trouver la méthode.Avec le temps, cela peut conduire à un compromis de conception pourri d’implémentation
isVisibleTo
à la foisCard
et dePlayer
classe, ce qui, à mon avis, est un non-non. Pourquoi Parce que j'imagine déjà le jour honteux oùplayer1.isVisibleTo(card1)
une valeur différente de celle quecard1.isVisibleTo(player1).
je pense est renvoyée - c'est subjectif - cela devrait être rendu impossible par la conception .Visibilité mutuelle des cartes et les joueurs doivent mieux être gouverné par une sorte d'un objet de contexte - que ce soit
Viewport
,Deal
ouGame
.Ce n'est pas égal à avoir des fonctions globales. Après tout, il peut y avoir beaucoup de jeux simultanés. Notez que la même carte peut être utilisée simultanément sur plusieurs tables. Devons-nous créer plusieurs
Card
instances pour chaque as de pique?Je pourrais toujours implémenter
isVisibleTo
dessusCard
, mais lui passer un objet de contexte et faireCard
déléguer la requête. Programmez l'interface pour éviter un couplage élevé.En ce qui concerne votre deuxième exemple - si l'ID de document est uniquement constitué d'un
BigDecimal
, pourquoi créer une classe wrapper?Je dirais que tout ce dont vous avez besoin est un
DocumentRepository.getDocument(BigDecimal documentID);
En passant, bien qu’ils soient absents de Java, il existe
struct
s en C #.Voir
http://msdn.microsoft.com/en-us/library/ah19swz4.aspx
http://msdn.microsoft.com/en-us/library/0taef578.aspx
pour référence. C'est un langage très orienté objet, mais personne n'en fait grand cas.
la source
Interface iObj = (Interface)obj;
, le comportement deiObj
n'est pas affecté par le statutstruct
ouclass
deobj
(sauf qu'il s'agira d'une copie encadrée à cette affectation si c'est astruct
).Vous commettez l’erreur commune de supposer que les classes sont un concept fondamental en POO. Les classes ne sont qu’un moyen particulièrement populaire d’encapsulation. Mais on peut permettre que ça glisse.
BON HEAVENS NO. Lorsque vous jouez à Bridge, demandez- vous aux sept de cœur quand il est temps de changer la main d'un mannequin d'un secret connu du mannequin à tout le monde? Bien sûr que non. Ce n'est pas une préoccupation de la carte du tout.
Les deux sont horribles; ne fais ni l'un ni l'autre. Ni le joueur ni la carte ne sont responsables de la mise en application des règles de Bridge!
Je n'avais jamais joué aux cartes avec un "viewport", je n'ai donc aucune idée de ce que cette classe est censée encapsuler. J'ai joué aux cartes avec les ponts couple de cartes, certains joueurs, une table, et une copie de Hoyle. Lequel de ces éléments représente-t-il?
Bien!
Non; L'idée de base de la programmation orientée objet est que les objets encapsulent leurs préoccupations . Dans votre système, une carte ne préoccupe pas beaucoup. Ni est un joueur. C'est parce que vous modélisez avec précision le monde . Dans le monde réel, les propriétés d'une carte qui sont pertinentes pour un jeu sont extrêmement simples. Nous pourrions remplacer les images sur les cartes par les nombres de 1 à 52 sans trop changer le jeu. Nous pourrions remplacer les quatre personnes par des mannequins marqués Nord, Sud, Est et Ouest sans trop changer le jeu. Les joueurs et les cartes sont les choses les plus simples du monde des jeux de cartes. Les règles sont ce qui est compliqué, de sorte que la classe qui représente les règles est celle où la complication devrait être.
Maintenant, si l’un de vos joueurs est une IA, son état interne peut être extrêmement compliqué. Mais cette IA ne détermine pas si elle peut voir une carte. Les règles le déterminent .
Voici comment je conçois votre système.
Tout d'abord, les cartes sont étonnamment compliquées s'il y a des jeux avec plus d'un jeu. Vous devez vous poser la question suivante: les joueurs peuvent-ils distinguer deux cartes du même rang? Si le joueur un joue l'un des sept coeurs et qu'il se passe des choses, puis que le joueur deux joue l'un des sept coeurs, le joueur trois peut-il déterminer qu'il s'agissait du même sept coeurs? Considérez ceci attentivement. Mais à part cela, les cartes devraient être très simples. ce ne sont que des données.
Ensuite, quelle est la nature d'un joueur? Un joueur consomme une séquence d' actions visibles et produit une action .
L'objet de règles est ce qui coordonne tout cela. Les règles produisent une séquence d'actions visibles et informent les joueurs:
Et demande ensuite au joueur une action.
Etc.
Séparez vos mécanismes de vos politiques . Les règles du jeu doivent être encapsulées dans un objet de règles , pas dans les cartes . Les cartes ne sont qu'un mécanisme.
la source
Vous avez raison de dire que le couplage des données et du comportement est l’idée centrale de la programmation orientée objet, mais c’est plus que cela. Par exemple, encapsulation : La programmation POO / modulaire nous permet de séparer une interface publique des détails d'implémentation. En POO, cela signifie que les données ne doivent jamais être accessibles au public et doivent uniquement être utilisées via des accesseurs. Selon cette définition, un objet sans méthode est en effet inutile.
Une classe qui n'offre pas de méthodes autres que des accesseurs est essentiellement une structure trop compliquée. Mais ce n’est pas mauvais, car la programmation orientée objet vous donne la possibilité de modifier les détails internes, ce qui n’est pas le cas d’une structure. Par exemple, au lieu de stocker une valeur dans un champ de membre, elle pourrait être recalculée à chaque fois. Ou bien un algorithme de sauvegarde est modifié et, avec lui, l'état à suivre.
Bien que la programmation orientée objet présente des avantages évidents (en particulier par rapport à la programmation procédurale simple), il est naïf de chercher à obtenir une programmation «pure». Certains problèmes ne correspondent pas bien à une approche orientée objet et sont résolus plus facilement par d'autres paradigmes. Lorsque vous rencontrez un tel problème, n'insistez pas pour une approche inférieure.
Envisagez de calculer la séquence de Fibonacci d’une manière orientée objet . Je ne peux pas penser à un moyen sensé de le faire; la programmation structurée simple offre la meilleure solution à ce problème.
Votre
isVisible
relation appartient aux deux classes, à l’une ou à l’autre, ou bien au contexte . Les enregistrements sans comportement sont typiques d'une approche de programmation fonctionnelle ou procédurale, ce qui semble être la solution la mieux adaptée à votre problème. Il n'y a rien de mal avecet il n'y a rien de mal à ne pas
Card
avoir de méthodes aurank
- delà et d'suit
accesseurs.la source
fibonacci
méthode sur une instance deinteger
. Je souhaite souligner votre point que OO concerne l’encapsulation, même dans des endroits apparemment petits. Laissez l'entier comprendre comment faire le travail. Plus tard, vous pourrez améliorer la mise en œuvre, ajouter du cache pour améliorer les performances. Contrairement aux fonctions, les méthodes suivent les données afin que tous les appelants bénéficient d'une implémentation améliorée. Peut-être que des entiers de précision arbitraire sont ajoutés plus tard, ils peuvent être traités de manière transparente comme des entiers normaux et peuvent avoir leur proprefibonacci
méthode de performance modifiée .Fibonacci
est une sous-classe de la classe abstraiteSequence
, la séquence est utilisée par n'importe quel ensemble de nombres et est responsable du stockage des graines, de l'état, de la mise en cache et d'un itérateur.C'est une question difficile, car elle repose sur plusieurs prémisses défectueuses:
Je ne parlerai pas beaucoup de la question n ° 1 à 3, car chacune pourrait engendrer sa propre réponse, ce qui appelle de nombreuses discussions basées sur des opinions. Mais je trouve l'idée de "POO concerne le couplage de données et de comportement" particulièrement troublante. Non seulement cela mène au n ° 4, mais aussi à l'idée que tout devrait être une méthode.
Il existe une différence entre les opérations qui définissent un type et les manières dont vous pouvez utiliser ce type. Le fait de pouvoir récupérer l'
i
élément th est essentiel au concept de tableau, mais le tri n'est que l'une des nombreuses choses que je peux choisir de faire avec un seul. Le tri n'a pas besoin d'être une méthode, pas plus que "créer un nouveau tableau contenant uniquement les éléments pairs" doit l'être.La POO concerne l'utilisation d'objets. Les objets ne sont qu’un moyen de parvenir à l’abstraction . L'abstraction est un moyen d'éviter un couplage inutile dans votre code, pas une fin en soi. Si votre notion de carte est définie uniquement par la valeur de sa suite et de son rang, vous pouvez l'implémenter en tant que simple tuple ou enregistrement. Il n'y a pas de détails non essentiels sur lesquels aucune autre partie du code pourrait former une dépendance. Parfois, vous n'avez rien à cacher.
Vous ne feriez pas
isVisible
une méthode duCard
type car la visibilité n'est pas essentielle à votre notion de carte (à moins que vous n'ayez des cartes très spéciales qui peuvent devenir translucides ou opaques ...). Devrait-il s'agir d'une méthode duPlayer
type? Eh bien, ce n'est probablement pas une qualité déterminante des joueurs non plus. Est-ce que cela devrait faire partie d'unViewport
type? Encore une fois, cela dépend de ce que vous définissez comme une fenêtre et de savoir si la vérification de la visibilité des cartes fait partie intégrante de la définition d’une fenêtre.C'est très possible
isVisible
devrait juste être une fonction gratuite.la source
Non, ils ne sont pas. Les objets Plain-Old-Data constituent un modèle parfaitement valide, et je les attendrais dans tous les programmes traitant de données devant être conservées ou communiquées entre des zones distinctes de votre programme.
Bien que votre couche de données puisse spouler une
Player
classe complète lorsqu'elle lit dans laPlayers
table, il peut s'agir plutôt d'une bibliothèque de données générale qui renvoie un POD avec les champs de la table, qu'elle transmet à une autre zone de votre programme qui convertit un joueur POD à votrePlayer
classe concrète .L'utilisation d'objets de données, dactylographiés ou non, peut ne pas avoir de sens dans votre programme, mais cela ne les rend pas anti-modèles. S'ils ont un sens, utilisez-les, et s'ils ne le font pas, non.
la source
Plain-Old-Data objects are a perfectly valid pattern
Je n'ai pas dit qu'ils ne l'étaient pas, je dis que c'est faux quand ils remplissent toute la moitié inférieure de l'application.Personnellement, je pense que Domain Driven Design aide à clarifier ce problème. La question que je pose est la suivante: comment décrire le jeu de cartes à un être humain? En d'autres termes, qu'est-ce que je modélise? Si la chose que je modélise inclut réellement le mot "viewport" et un concept qui correspond à son comportement, je créerais l'objet viewport et le ferais faire ce qu'il devrait logiquement.
Cependant, si je n'ai pas le concept de la fenêtre d'affichage sur mon jeu, et c'est quelque chose que je pense avoir besoin, sinon le code "se sent mal". Je réfléchis à deux fois avant d'y ajouter mon modèle de domaine.
Le mot modèle signifie que vous construisez une représentation de quelque chose. J'avertis de ne pas mettre dans une classe qui représente quelque chose d'abstrait au-delà de ce que vous représentez.
Je vais éditer pour ajouter qu'il est possible que vous ayez besoin du concept de Viewport dans une autre partie de votre code, si vous avez besoin d'une interface avec un affichage. Mais en termes de DDD, il s’agit d’une question d’infrastructure et existerait en dehors du modèle de domaine.
la source
Je ne fais habituellement pas d’auto-promotion, mais le fait est que j’ai beaucoup écrit sur les problèmes de conception de POO sur mon blog . Pour résumer plusieurs pages: vous ne devriez pas commencer à concevoir avec des classes. À partir d'interfaces ou d'API et du code de forme à partir de là, les chances de fournir des abstractions significatives, des spécifications d'ajustement et d'éviter le gonflement des classes concrètes avec du code non réutilisable sont plus grandes.
Comment cela s'applique à
Card
-Player
problème: Création d' uneViewPort
abstraction logique si vous pensezCard
etPlayer
comme deux bibliothèques indépendantes (qui impliqueraitPlayer
est parfois utilisé sansCard
). Cependant, je suis enclin à penser à unePlayer
caleCards
et devrait leur fournir unCollection<Card> getVisibleCards ()
accesseur. Ces deux solutions (ViewPort
et le mien) sont mieux que d' offririsVisible
comme méthodeCard
ouPlayer
, en termes de création de relations de code compréhensibles.Une solution hors classe est bien meilleure pour le
DocumentId
. Il y a peu de motivation à faire dépendre (fondamentalement, un entier) d'une bibliothèque de base de données complexe.la source
Je ne suis pas sûr que la question soit résolue au bon niveau. J'avais exhorté les sages du forum à réfléchir activement au cœur de la question.
U Mad évoque une situation dans laquelle il pense que programmer selon sa compréhension de la POO aurait généralement pour conséquence qu'un grand nombre de nœuds d'extrémité seraient des détenteurs de données, tandis que son API de niveau supérieur est responsable de la plupart des comportements.
Je pense que le sujet est devenu légèrement tangent pour savoir si isVisible serait défini sur Card vs Player; Ce n'était qu'un exemple illustré, bien que naïf.
J'avais poussé l'expérimenté ici à regarder le problème actuel cependant. Je pense qu'il y a une bonne question pour laquelle U Mad a insisté. Je comprends que vous pousseriez les règles et la logique concernée vers un objet qui lui est propre. mais comme je comprends la question est
Mon avis:
Je pense que vous posez une question de granularité difficile à résoudre en programmation orientée objet. Dans ma petite expérience, je n'inclurais pas une entité dans mon modèle qui n'inclut aucun comportement en soi. Si je devais le faire, j’avais probablement utilisé une structure qui soit conçue pour contenir une telle abstraction contrairement à une classe qui a l’idée d’encapsuler des données et un comportement.
la source
Point
withgetX()
. Vous pouvez imaginer que l'un de ses attributs lui est attribué, mais il pourrait également le lire à partir d'un disque ou d'Internet. Obtenir et mettre en place est un comportement, et avoir des cours qui font cela est tout à fait correct. Les bases de données obtiennent et définissent uniquement les données fwiwUne source commune de confusion en POO provient du fait que de nombreux objets encapsulent deux aspects de l’état: les choses qu’ils connaissent et les choses qui les connaissent. Les discussions sur l'état des objets ignorent souvent ce dernier aspect, car dans les cadres où les références d'objets sont proches les unes des autres, il n'y a pas de moyen général de déterminer ce que l'on peut savoir sur un objet dont la référence a déjà été exposée au monde extérieur.
Je suggérerais qu'il serait probablement utile d'avoir un
CardEntity
objet qui encapsule ces aspects de la carte dans des composants séparés. Une composante serait liée aux marques sur la carte (par exemple, "Diamond King" ou "Explosion de lave; les joueurs ont une chance d'ES-3 à esquiver ou sinon subissent des dégâts 2D6"). On pourrait se rapporter à un aspect unique de l’état tel que la position (par exemple, il est dans le jeu, dans la main de Joe ou sur la table devant Larry). Un troisième peut se rapporter à peut voir (peut-être personne, peut-être un joueur, ou peut-être plusieurs joueurs). Pour que tout soit synchronisé, les endroits où une carte pourrait être ne seraient pas encapsulés sous forme de simples champs, mais plutôt d'CardSpace
objets; déplacer une carte dans un espace, on lui donnerait une référence au bonCardSpace
objet; il se retirerait alors de l'ancien espace et se placerait dans le nouvel espace).Encapsuler explicitement "qui sait à propos de X" séparément de "ce que X sait" devrait aider à éviter beaucoup de confusion. Il faut parfois veiller à éviter les fuites de mémoire, en particulier avec de nombreuses associations (par exemple, si de nouvelles cartes peuvent exister et que les anciennes disparaissent, vous devez vous assurer que les cartes qui devraient être abandonnées ne sont pas laissées définitivement attachées à des objets de longue durée ) mais si l’existence de références à un objet constitue une partie pertinente de son état, il est tout à fait normal que l’objet lui-même encapsule explicitement ces informations (même s’il délègue à une autre classe le travail de sa gestion).
la source
Et comment est-ce mauvais / mal avisé?
Pour utiliser une analogie similaire avec votre exemple de cartes, considérons a
Car
, aDriver
et vous devez déterminer si ellesDriver
peuvent conduire leCar
.OK, alors vous avez décidé que vous ne vouliez
Car
pas savoir si laDriver
clé de voiture était la bonne ou non, et pour une raison inconnue, vous avez également décidé de ne pas vouloir que votre cours soitDriver
connuCar
(vous n'avez pas tout à fait raison ceci dans votre question initiale aussi). Par conséquent, vous avez une classe intermédiaire, quelque chose qui ressemble à uneUtils
classe, qui contient la méthode avec les règles de gestion afin de renvoyer uneboolean
valeur pour la question ci-dessus.Je pense que ça va. La classe intermédiaire peut ne plus avoir besoin de vérifier les clés de la voiture à présent, mais elle peut être modifiée pour déterminer si le conducteur possède un permis de conduire valide, sous l'influence de l'alcool ou dans un avenir dystopique, pour vérifier la biométrie de l'ADN. Par encapsulation, il n’ya vraiment pas de gros problème de coexistence de ces trois classes.
la source