Dans le code existant, je vois parfois des classes qui ne sont que des wrappers pour les données. quelque chose comme:
class Bottle {
int height;
int diameter;
Cap capType;
getters/setters, maybe a constructor
}
Ma compréhension de OO est que les classes sont des structures pour les données et les méthodes d'exploitation sur ces données. Cela semble exclure les objets de ce type. Pour moi, ils ne sont rien de plus qu'une structs
sorte de défaite pour le but de OO. Je ne pense pas que ce soit nécessairement mauvais, bien que cela puisse être une odeur de code.
Existe-t-il un cas où de tels objets seraient nécessaires? Si cela est utilisé souvent, est-ce que cela rend la conception suspecte?
java
code-quality
object-oriented
code-smell
Michael K
la source
la source
Réponses:
Certainement pas le mal et pas une odeur de code dans mon esprit. Les conteneurs de données sont des citoyens OO valides. Parfois, vous souhaitez encapsuler des informations connexes. C'est beaucoup mieux d'avoir une méthode comme
que
L'utilisation d'une classe vous permet également d'ajouter un paramètre supplémentaire à Bottle sans modifier tous les appelants de DoStuffWithBottle. Et vous pouvez sous-classer Bottle et augmenter encore la lisibilité et l'organisation de votre code, si nécessaire.
Il existe également des objets de données simples qui peuvent être renvoyés à la suite d'une requête de base de données, par exemple. Je crois que le terme pour eux dans ce cas est "objet de transfert de données".
Dans certaines langues, il y a aussi d'autres considérations. Par exemple, en C #, les classes et les structures se comportent différemment, puisque les structures sont un type de valeur et que les classes sont des types de référence.
la source
DoStuffWith
devrait être une méthode de laBottle
classe en POO (et devrait très probablement être immuable, aussi). Ce que vous avez écrit ci-dessus n'est pas un bon modèle dans un langage OO (sauf si vous vous connectez avec une API héritée). Il s'agit toutefois d'une conception valide dans un environnement non OO.Les classes de données sont valables dans certains cas. Les DTO sont un bon exemple cité par Anna Lear. En général cependant, vous devriez les considérer comme la graine d'une classe dont les méthodes n'ont pas encore germé. Et si vous en rencontrez beaucoup dans l'ancien code, traitez-les comme une forte odeur de code. Ils sont souvent utilisés par de vieux programmeurs C / C ++ qui n’ont jamais effectué la transition vers la programmation OO et sont un signe de programmation procédurale. Vous pouvez avoir des ennuis avant même de vous fier à des getters et des setters (ou pire encore, à un accès direct à des membres non privés). Prenons un exemple de méthode externe nécessitant des informations
Bottle
.Voici
Bottle
une classe de données):Ici nous avons donné
Bottle
un comportement):La première méthode enfreint le principe "Ne pas demander", et en gardant l'
Bottle
idiot, nous avons laissé des connaissances implicites sur les bouteilles, telles que ce qui fait qu'un fragment (leCap
), glisse dans une logique en dehors de laBottle
classe. Vous devez être sur vos gardes pour prévenir ce type de «fuite» lorsque vous comptez habituellement sur les getters.La seconde méthode
Bottle
ne demande que ce dont elle a besoin pour faire son travail et laisseBottle
décider si elle est fragile ou plus grande qu'une taille donnée. Le résultat est un couplage beaucoup plus souple entre la méthode et la mise en œuvre de Bottle. Un effet secondaire agréable est que la méthode est plus propre et plus expressive.Vous utiliserez rarement autant de champs d'un objet sans écrire une logique qui devrait résider dans la classe contenant ces champs. Déterminez quelle est cette logique et déplacez-la là où elle se trouve.
la source
Si c'est le genre de chose dont vous avez besoin, c'est bien, mais s'il vous plaît, s'il vous plaît, s'il vous plaît, faites-le comme
au lieu de quelque chose comme
S'il vous plaît.
la source
Comme @Anna a dit, certainement pas le mal. Bien sûr, vous pouvez mettre des opérations (méthodes) dans des classes, mais seulement si vous le souhaitez . Tu n'es pas obligé .
Permettez-moi un petit reproche à propos de l’idée qu’il faut placer des opérations dans des classes, ainsi que de l’idée que les classes sont des abstractions. En pratique, cela encourage les programmeurs à
Créez plus de classes que nécessaire (structure de données redondante). Lorsqu'une structure de données contient plus de composants que le minimum nécessaire, elle n'est pas normalisée et contient donc des états incohérents. En d’autres termes, lorsqu’il est modifié, il doit être modifié à plus d’un endroit pour rester cohérent. L'échec à effectuer toutes les modifications coordonnées le rend incohérent et constitue un bogue.
Résolvez le problème 1 en définissant des méthodes de notification afin que, si la partie A est modifiée, elle tente de propager les modifications nécessaires aux parties B et C. Il est donc essentiel de recourir à des méthodes d’accesseur d’obtention et de définition. Comme il s'agit d'une pratique recommandée, il semble que le problème 1 soit excusé, ce qui entraîne davantage de problèmes 1 et plus de solution 2. Cela entraîne non seulement des bogues dus à une mise en œuvre incomplète des notifications, mais également à un problème de performances qui entrave les notifications. Ce ne sont pas des calculs infinis, mais des calculs très longs.
Ces concepts sont enseignés comme de bonnes choses, généralement par des enseignants qui n’ont pas eu à travailler avec des millions d’applications monstres monstres confrontées à ces problèmes.
Voici ce que j'essaie de faire:
Conservez les données aussi normalisées que possible. Ainsi, lorsqu'une modification est apportée aux données, elle est effectuée avec le moins de points de code possible, afin de minimiser la probabilité d'entrer un état incohérent.
Lorsque les données ne doivent pas être normalisées et que la redondance est inévitable, n'utilisez pas de notifications pour tenter de les maintenir cohérentes. Plutôt tolérer une incohérence temporaire. Résolvez les incohérences avec le balayage périodique des données par un processus ne faisant que cela. Cela centralise la responsabilité du maintien de la cohérence, tout en évitant les problèmes de performances et de correction auxquels les notifications sont sujettes. Il en résulte un code beaucoup plus petit, sans erreur et efficace.
la source
Ce type de classes est très utile lorsque vous utilisez des applications de taille moyenne / volumineuse, pour certaines raisons:
Donc, pour résumer, selon mon expérience, ils sont généralement plus utiles que gênants.
la source
Avec la conception du jeu, les milliers d’appels de fonction et les écouteurs d’événements peuvent parfois valoir la peine d’avoir des classes qui stockent uniquement des données et d’autres classes qui parcourent toutes les classes de données uniquement pour exécuter la logique.
la source
D'accord avec Anna Lear,
Parfois, les gens oublient de lire les conventions de codage Java de 1999 qui expliquent clairement que ce type de programmation convient parfaitement. En fait, si vous l'évitez, votre code va sentir! (trop de getters / setters)
D'après les conventions de code Java 1999: Un exemple de variables d'instance publique appropriées est le cas où la classe est essentiellement une structure de données, sans comportement. En d'autres termes, si vous avez utilisé une structure au lieu d'une classe (si la structure est supportée par Java), il est approprié de rendre publiques les variables d'instance de la classe. http://www.oracle.com/technetwork/java/javase/documentation/codeconventions-137265.html#177
Utilisés correctement, les POD (structures de données anciennes) sont meilleurs que les POJO, tout comme les POJO sont souvent meilleurs que les EJB.
http://en.wikipedia.org/wiki/Plain_Old_Data_Structures
la source
Les structures ont leur place, même en Java. Vous ne devriez les utiliser que si les deux choses suivantes sont vraies:
Si tel est le cas, vous devez alors rendre les champs publics et ignorer les getters / setters. De toute façon, les accesseurs et les setters sont maladroits, et Java est idiot de ne pas avoir de propriétés comme un langage utile. Comme votre objet de type struct ne devrait de toute façon pas utiliser de méthode, les champs publics ont le plus de sens.
Toutefois, si l’un ou l’autre de ces critères ne s’applique pas, vous avez affaire à une véritable classe. Cela signifie que tous les champs doivent être privés. (Si vous avez absolument besoin d'un champ dont la portée est plus accessible, utilisez un getter / setter.)
Pour vérifier si votre supposée-structure a un comportement, regardez quand les champs sont utilisés. Si cela semble violer tell, ne demandez pas , alors vous devez déplacer ce comportement dans votre classe.
Si certaines de vos données ne doivent pas changer, vous devez alors rendre tous ces champs définitifs. Vous pourriez envisager de rendre votre classe immuable . Si vous devez valider vos données, fournissez une validation dans les setters et les constructeurs. (Une astuce utile consiste à définir un poseur privé et à modifier votre champ au sein de votre classe en utilisant uniquement ce sélecteur.)
Votre exemple de bouteille échouerait probablement aux deux tests. Vous pourriez avoir un code (artificiel) qui ressemble à ceci:
Au lieu de cela, il devrait être
Si vous changiez la hauteur et le diamètre, s'agirait-il de la même bouteille? Probablement pas. Celles-ci devraient être finales. Une valeur négative est-elle acceptable pour le diamètre? Votre bouteille doit-elle être plus grande que large? Le cap peut-il être nul? Non? Comment validez-vous cela? Supposons que le client soit stupide ou méchant. ( Il est impossible de faire la différence. ) Vous devez vérifier ces valeurs.
Voici à quoi pourrait ressembler votre nouvelle classe Bottle:
la source
IMHO il n'y a souvent pas assez de classes comme celle-ci dans des systèmes fortement orientés objet. Je dois soigneusement qualifier cela.
Bien sûr, si les champs de données ont une portée et une visibilité étendues, cela peut être extrêmement indésirable s’il existe des centaines, voire des milliers, d’endroits dans votre base de code manipulant de telles données. C'est demander des ennuis et des difficultés à maintenir des invariants. Cependant, cela ne signifie pas pour autant que chaque classe de la base de code profite de la dissimulation d'informations.
Mais il existe de nombreux cas où ces champs de données auront une portée très étroite. Un exemple très simple est une
Node
classe privée d'une structure de données. Cela peut souvent simplifier énormément le code en réduisant le nombre d'interactions entre objets, si celles-ciNode
pouvaient simplement consister en données brutes. Cela sert de mécanisme de découplage car la version alternative pourrait nécessiter un couplage bidirectionnel, par exemple,Tree->Node
etNode->Tree
par opposition à simpleTree->Node Data
.Un exemple plus complexe serait celui des systèmes à composants d'entités, tels qu'ils sont souvent utilisés dans les moteurs de jeux. Dans ces cas, les composants ne sont souvent que des données brutes et des classes similaires à celle que vous avez montrée. Cependant, leur portée / visibilité a tendance à être limitée car il n’ya généralement qu’un ou deux systèmes pouvant accéder à ce type de composant particulier. En conséquence, il est toujours assez facile de maintenir des invariants dans ces systèmes et, de plus, ces systèmes ont très peu d’
object->object
interactions, ce qui facilite la compréhension de ce qui se passe au niveau de l’oiseau.Dans de tels cas, vous pouvez vous retrouver avec quelque chose de plus semblable aux interactions (ce diagramme indique les interactions, pas le couplage, car un diagramme de couplage peut inclure des interfaces abstraites pour la deuxième image ci-dessous):
... par opposition à ceci:
... et le premier type de système tend à être beaucoup plus facile à maintenir et à raisonner en termes d’exactitude, en dépit du fait que les dépendances vont effectivement vers les données. Vous obtenez beaucoup moins de couplage principalement parce que beaucoup de choses peuvent être transformées en données brutes au lieu que les objets interagissent les uns avec les autres pour former un graphique très complexe d'interactions.
la source
Il existe même de nombreuses créatures étranges dans le monde de la programmation orientée objet. J'ai déjà créé une classe, abstraite, qui ne contient rien, juste pour être un parent commun de deux autres classes ... c'est la classe Port:
Donc SceneMember est la classe de base, il fait tout le travail qui doit apparaître sur la scène: ajouter, supprimer, obtenir un ID, etc. Le message est la connexion entre les ports, il a sa propre vie. InputPort et OutputPort contiennent leurs propres fonctions spécifiques.
Le port est vide. Il sert simplement à regrouper InputPort et OutputPort pour, par exemple, une liste de ports. Il est vide car SceneMember contient tous les éléments permettant d'agir en tant que membre, et InputPort et OutputPort contiennent les tâches spécifiées par le port. InputPort et OutputPort sont tellement différents qu’ils n’ont pas de fonctions communes (à l’exception de SceneMember). Donc, le port est vide. Mais c'est légal.
C'est peut-être un anti - modèle , mais j'aime bien.
(C'est comme le mot "compagnon", qui est utilisé à la fois pour "femme" et "mari". Vous n'utilisez jamais le mot "partenaire" pour désigner une personne concrète, car vous connaissez son sexe, et cela n'a pas d'importance. si il / elle est marié ou non, vous utilisez donc "quelqu'un" à la place, mais il existe toujours et est utilisé dans de rares situations abstraites, par exemple un texte de loi.)
la source