Objets à comportement zéro en POO - mon dilemme de conception

94

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 Cardclasse doit déterminer la visibilité pour les joueurs.

Une façon est d'avoir boolean isVisible(Player p)sur la Cardclasse.

Une autre est d'avoir boolean isVisible(Card c)sur la Playerclasse.

Je n'aime pas particulièrement la première approche, qui accorde des connaissances sur les Playerclasses supérieures à une Cardclasse inférieure .

Au lieu de cela, j'ai opté pour la troisième option où nous avons une Viewportclasse qui, à partir d' Playerune liste de cartes, détermine quelles cartes sont visibles.

Cependant, cette approche prive les classes Cardet les Playerclasses d'une fonction membre possible. Une fois que vous faites cela pour d' autres choses que la visibilité des cartes, il vous reste Cardet les Playerclasses 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 Viewportci - 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 DocumentIdimmuable, qu’un seul BigDecimal idmembre et un getter pour ce membre. Maintenant, vous devez avoir une méthode quelque part, qui donne un DocumentIdretour Documentpour cet identifiant depuis une base de données.

Le faites vous:

  • Ajoutez une Document getDocument(SqlSession)méthode à la DocumentIdclasse, 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 la DocumentIdclasse morte, sans comportement, comme une classe semblable à la structure.
RokL
la source
21
Certaines de vos prémisses ici sont complètement fausses, ce qui va rendre très difficile la réponse à la question sous-jacente. Gardez vos questions aussi concises et sans opinion que possible et vous obtiendrez de meilleures réponses.
pdr le
31
"Cela va clairement à l'encontre de l'idée principale de la programmation orientée objet" - non, ce n'est pas, mais c'est une erreur commune.
Doc Brown le
5
Je suppose que le problème réside dans le fait qu'il y a eu différentes écoles pour "Orientation d'objet" dans le passé - comme cela avait été initialement conçu par des personnes comme Alan Kay (voir geekswithblogs.net/theArchitectsNapkin/archive/2013/09/08/ … ), Et la manière dont cela a été enseigné dans le contexte de OOA / OOD par des personnes de Rational ( en.wikipedia.org/wiki/Object-oriented_analysis_and_design ).
Doc Brown le
21
C'est une très bonne question, et bien posée - contrairement à certains des autres commentaires, je dirais. Cela montre clairement à quel point la plupart des conseils sur la structure du programme sont naïfs ou incomplets - et combien il est difficile de le faire, et combien une conception adéquate est inaccessible dans de nombreuses situations, quel que soit le but recherché. Et bien que la multi-méthode soit une réponse évidente à cette question spécifique, le problème fondamental de la conception persiste.
Thiago Silva
5
Qui a dit que les classes sans courage sont un anti-modèle?
James Anderson

Réponses:

42

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 Cardsavoir à quoi Playeril est visible?

Et pourquoi mettre en œuvre Card.isVisibleTo(Player p), mais pas Player.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 Playerplus élevé qu'un Card(?) - - 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 fois Card et de Playerclasse, 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 que card1.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, Dealou Game.

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 Cardinstances pour chaque as de pique?

Je pourrais toujours implémenter isVisibleTodessus Card, mais lui passer un objet de contexte et faire Carddé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 structs en C #.

Voir

pour référence. C'est un langage très orienté objet, mais personne n'en fait grand cas.

Konrad Morawski
la source
1
Juste une remarque à propos des structures en C #: Ce ne sont pas des structures typiques, comme vous les connaissez en C. En fait, elles prennent également en charge la POO avec héritage, encapsulation et polymorphisme. Outre certaines particularités, la principale différence réside dans la manière dont le moteur d'exécution gère les instances lorsqu'elles sont transmises à d'autres objets: les structures sont des types de valeur et les classes sont des types de référence!
Aschratt
3
@Aschratt: Les structures ne supportent pas l'héritage. Les structures peuvent implémenter des interfaces, mais les structures implémentant se comportent différemment des objets de classe qui agissent de la même manière. Bien qu'il soit possible de faire en sorte que les structures se comportent un peu comme des objets, le meilleur cas d'utilisation des structures est celui qui se comporte comme une structure C et que l'on encapsule sous forme de primitives ou de types de classe immuables.
Supercat
1
+1 pour pourquoi "ce n'est pas égal à avoir des fonctions globales." Cela n'a pas été beaucoup abordé par d'autres. (Bien que si vous avez plusieurs decks, une fonction globale renverrait toujours des valeurs différentes pour des instances distinctes de la même carte).
alexis
@supercat Cela mérite une question distincte ou une session de discussion en direct, mais cela ne m'intéresse pas non plus :-( Vous dites (en C #) "les structures qui implémentent des interfaces se comportent différemment des objets de classe qui fonctionnent de la même manière". Je conviens que il existe d'autres différences de comportement à prendre en compte, mais d'après ce que je sais Interface iObj = (Interface)obj;, le comportement de iObjn'est pas affecté par le statut structou classde obj(sauf qu'il s'agira d'une copie encadrée à cette affectation si c'est a struct).
Mark Hurd
150

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.

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.

Supposons que vous ayez une API de jeu de cartes générale. Vous avez une carte de classe. Maintenant, cette classe de cartes doit déterminer la visibilité pour les joueurs.

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.

Une façon est d'avoir un booléen isVisible (Player p) sur la classe de carte. Une autre solution consiste à avoir une valeur booléenne isVisible (carte c) dans la classe Player.

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!

Au lieu de cela, j'ai opté pour la troisième option où nous avons une classe Viewport qui, à partir d'un joueur et d'une liste de cartes, détermine quelles cartes sont visibles.

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?

Cependant, cette approche prive les classes de cartes et de joueurs d’une fonction membre possible.

Bien!

Une fois que vous faites cela pour des choses autres que la visibilité des cartes, il ne vous reste plus que des classes Card et Player qui contiennent uniquement des données car toutes les fonctionnalités sont implémentées dans d'autres classes, qui sont pour la plupart des classes sans données, juste des méthodes, comme le Viewport ci-dessus. Cela va clairement à l’encontre de l’idée principale de la programmation orientée objet.

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:

  • Joueur un, les dix cœurs vous ont été remis par le joueur trois.
  • Joueur deux, une carte a été remise au joueur un par le joueur trois.

Et demande ensuite au joueur une action.

  • Joueur 1, que veux-tu faire?
  • Le joueur 1 dit: triple le fromp.
  • Joueur 1, il s’agit d’une action illégale puisqu’un point sur trois produit un gambit indéfendable.
  • Joueur 1, que veux-tu faire?
  • Le joueur 1 dit: défaussez la reine des piques.
  • Joueur deux, le joueur un a jeté la reine de pique.

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.

Eric Lippert
la source
41
@gnat: Alan Kay a exprimé un point de vue opposé: "En fait, j'ai composé le terme" orienté objet "et je peux vous dire que je n'avais pas à l'esprit le C ++." Il existe des langages OO sans classes; JavaScript vient à l'esprit.
Eric Lippert
19
@gnat: Je suis d'accord pour dire que JS, tel qu'il se présente aujourd'hui, n'est pas un bon exemple de langage POO, mais cela montre qu'il est très facile de construire un langage OO sans cours. Je conviens que l'unité fondamentale de OO-ness dans Eiffel et C ++ est la classe; Ce que je ne partage pas est la notion selon laquelle les classes sont la condition sine qua non de OO. La condition sine qua non de OO consiste en des objets qui encapsulent le comportement et communiquent entre eux via une interface publique bien définie.
Eric Lippert le
16
Je suis d’accord avec @EricLippert, les classes ne sont pas fondamentales pour OO, pas plus que l’héritage, quoi que l’on puisse dire. L'encapsulation des données, du comportement et de la responsabilité est cependant accomplie. Il existe des langages basés sur des prototypes au- delà de Javascript qui sont OO mais sans classe. C’est une erreur en particulier que de privilégier l’héritage par rapport à ces concepts. Cela dit, les classes sont un moyen très utile d’organiser l’encapsulation du comportement. Le fait que vous puissiez traiter les classes en tant qu'objets (et dans les langages prototypes, inversement) rend la ligne floue.
Schwern
6
Considérez-le ainsi: quel comportement une carte réelle, dans le monde réel, affiche-t-elle par elle-même? Je pense que la réponse est "aucune". D'autres choses agissent sur la carte. La carte elle-même, dans le monde réel, n’est littéralement qu’une information (4 des clubs), sans aucun comportement intrinsèque. La manière dont cette information (alias "une carte") est utilisée est à 100% jusqu'à quelque chose / quelqu'un d'autre, alias les "règles" et les "joueurs". Les mêmes cartes peuvent être utilisées pour une variété infinie (bien, peut-être pas tout à fait) de jeux différents, par un nombre quelconque de joueurs différents. Une carte est juste une carte, et tout ce qu’elle a sont des propriétés.
Craig
5
@Montagist: Laissez-moi clarifier un peu les choses alors. Considérez C. Je pense que vous seriez d'accord pour dire que C n'a pas de cours. Cependant, vous pouvez dire que les structures sont des "classes", vous pouvez créer des champs de type pointeur de fonction, vous pouvez créer des vtables, vous pouvez créer des méthodes appelées "constructeurs" qui configurent les vtables de sorte que certaines structures "héritent" les unes des autres, etc. Vous pouvez émuler un héritage basé sur une classe en C. Et vous pouvez l' émuler dans JS. Mais cela signifie construire quelque chose en plus du langage qui n’est pas déjà là.
Eric Lippert
29

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 isVisiblerelation 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 avec

    static boolean isVisible(Card c, Player p);
    

    et il n'y a rien de mal à ne pas Cardavoir de méthodes au rank- delà et d' suitaccesseurs.

Amon
la source
11
@ Humad oui, c'est exactement ce que je veux dire, et il n'y a rien de mal à cela. Utilisez le bon paradigme <del> language </ del> pour le travail à effectuer. (En passant, la plupart des langages autres que Smalltalk ne sont pas orientés purement objet. Par exemple, Java, C # et C ++ prennent en charge la programmation impérative, structurée, procédurale, modulaire, fonctionnelle et orientée objet. Tous ces paradigmes non-OO sont disponibles pour une raison : afin que vous puissiez les utiliser)
amon
1
Il existe un moyen OO raisonnable de faire Fibonacci, appelez la fibonacciméthode sur une instance de integer. 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 propre fibonacciméthode de performance modifiée .
Schwern
2
@Schwern Si quelque chose Fibonacciest une sous-classe de la classe abstraite Sequence, 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.
George Reith le
2
Je ne m'attendais pas à ce que le «pur FOP Fibonacci» soit aussi efficace pour le snip-nerd . Arrêtez toute discussion circulaire à ce sujet dans ces commentaires, même si cela avait une certaine valeur de divertissement. Maintenant, faisons tous quelque chose de constructif pour un changement!
amon
3
il serait idiot de faire de Fibonacci une méthode d’entiers, afin que vous puissiez dire que c’est la POO. C'est une fonction et devrait être traitée comme une fonction.
user253751
19

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. (...) Cela va clairement à l’encontre de l’idée principale de la programmation orientée objet.

C'est une question difficile, car elle repose sur plusieurs prémisses défectueuses:

  1. L'idée que le POO est le seul moyen valide d'écrire du code.
  2. L'idée que la POO est un concept bien défini. C'est devenu un mot tellement en vogue qu'il est difficile de trouver deux personnes capables de s'entendre sur la POO.
  3. L'idée que la POO concerne le regroupement de données et de comportements.
  4. L'idée que tout est / devrait être une abstraction.

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 isVisibleune méthode du Cardtype 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 du Playertype? Eh bien, ce n'est probablement pas une qualité déterminante des joueurs non plus. Est-ce que cela devrait faire partie d'un Viewporttype? 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 isVisibledevrait juste être une fonction gratuite.

Doval
la source
1
+1 pour le sens commun au lieu de bourdonnement aveugle.
Droitier
D'après les lignes que j'ai lues, l'essai que vous avez lié ressemble à celui d'une lecture solide que je n'ai pas encore eue.
Arthur Havlicek
@ ArthurHavlicek Il est difficile de suivre si vous ne comprenez pas les langues utilisées dans l'exemple de code, mais je l'ai trouvé assez éclairant.
Doval
9

É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.

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 Playerclasse complète lorsqu'elle lit dans la Playerstable, 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 à votre Playerclasse 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.

DougM
la source
5
Ne désapprouvez rien de ce que vous avez dit, mais cela ne répond pas du tout à la question. Cela dit, je blâme la question plus que la réponse.
pdr le
2
Exactement, les cartes et les documents ne sont que des conteneurs d’informations, même dans le monde réel, et tout "motif" qui ne peut pas le gérer doit être ignoré.
JeffO
1
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.
RokL
8

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.

RibaldEddie
la source
La réponse de Lippert ci-dessus est un meilleur exemple de ce concept.
RibaldEddie
5

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- Playerproblème: Création d' une ViewPortabstraction logique si vous pensez Cardet Playercomme deux bibliothèques indépendantes (qui impliquerait Playerest parfois utilisé sans Card). Cependant, je suis enclin à penser à une Playercale Cardset devrait leur fournir un Collection<Card> getVisibleCards ()accesseur. Ces deux solutions ( ViewPortet le mien) sont mieux que d' offrir isVisiblecomme méthode Cardou Player, 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.

Arthur Havlicek
la source
J'aime ton blog.
RokL
3

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

  1. Est-il acceptable d’avoir des constructions simples de détenteurs de données (classes / structures; peu importe la façon dont elles sont modélisées pour cette question) qui n’offrent pas vraiment beaucoup de fonctionnalités?
  2. Si oui, quel est le meilleur moyen ou le meilleur moyen de les modéliser?
  3. Si non, comment pouvons-nous incorporer ces parties de compteur de données dans des classes API supérieures (y compris le comportement)

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.

Harsha
la source
3
Le problème est que la question (et votre réponse) concerne la manière de faire les choses "en général". Le fait est que nous ne faisons jamais les choses "en général". Nous faisons toujours des choses spécifiques. Il est nécessaire d’examiner nos spécificités et de les mesurer par rapport à nos exigences afin de déterminer si nos spécificités sont adaptées à la situation.
John Saunders
@JohnSaunders Je perçois votre sagesse ici et suis d'accord dans une certaine mesure, mais une simple approche conceptuelle est nécessaire avant de traiter un problème. Après tout, la question ici n’est pas aussi ouverte qu’elle semble être. Je pense que la question OOD est valide pour tous les concepteurs OO dans les utilisations initiales de la programmation orientée objet. Quelle est votre prise? Si une concrétion vous aide, nous pourrions discuter de la construction d'un exemple de votre choix.
Harsha
Je suis sorti de l'école depuis plus de 35 ans maintenant. Dans le monde réel, je trouve très peu de valeur dans les "approches conceptuelles". Je trouve que l'expérience est un meilleur enseignant que Meyers dans ce cas.
John Saunders
Je ne comprends pas vraiment la classe donnée / classe pour la distinction comportementale. Si vous abstenez correctement vos objets, il n'y a pas de distinction. Imaginez une fonction Pointwith getX(). 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 fwiw
Arthur Havlicek le
@ ArthurHavlicek: Savoir ce qu'une classe ne fera pas est souvent aussi utile que de savoir ce qu'elle va faire. Il est utile que quelque chose spécifie dans son contrat qu'il ne se comportera pas davantage comme un détenteur de données immuable partageable, ou simplement comme un détenteur de données modifiable non partageable.
Supercat
2

Une 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 CardEntityobjet 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' CardSpaceobjets; déplacer une carte dans un espace, on lui donnerait une référence au bonCardSpaceobjet; 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).

supercat
la source
0

Cependant, cette approche prive les classes de cartes et de joueurs d’une fonction membre possible.

Et comment est-ce mauvais / mal avisé?

Pour utiliser une analogie similaire avec votre exemple de cartes, considérons a Car, a Driveret vous devez déterminer si elles Driverpeuvent conduire le Car.

OK, alors vous avez décidé que vous ne vouliez Carpas savoir si la Driverclé de voiture était la bonne ou non, et pour une raison inconnue, vous avez également décidé de ne pas vouloir que votre cours soit Driverconnu Car(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 à une Utilsclasse, qui contient la méthode avec les règles de gestion afin de renvoyer une booleanvaleur 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.

hjk
la source