Par exemple, une classe a généralement des membres de classe et des méthodes, par exemple:
public class Cat{
private String name;
private int weight;
private Image image;
public void printInfo(){
System.out.println("Name:"+this.name+",weight:"+this.weight);
}
public void draw(){
//some draw code which uses this.image
}
}
Mais après avoir lu sur le principe de responsabilité unique et le principe ouvert et fermé, je préfère séparer une classe en DTO et une classe auxiliaire avec des méthodes statiques uniquement, par exemple:
public class CatData{
public String name;
public int weight;
public Image image;
}
public class CatMethods{
public static void printInfo(Cat cat){
System.out.println("Name:"+cat.name+",weight:"+cat.weight);
}
public static void draw(Cat cat){
//some draw code which uses cat.image
}
}
Je pense que cela correspond au principe de responsabilité unique, car maintenant la responsabilité de CatData est de conserver uniquement les données, ne se soucie pas des méthodes (également pour CatMethods). Et il correspond également au principe ouvert fermé, car l'ajout de nouvelles méthodes n'a pas besoin de modifier la classe CatData.
Ma question est, est-ce un bon ou un anti-modèle?
Réponses:
Vous avez montré deux extrêmes ("tout ce qui est privé et toutes les méthodes (peut-être sans rapport) dans un seul objet" vs "tout ce qui est public et aucune méthode à l'intérieur de l'objet"). À mon humble avis, la bonne modélisation OO n'en fait pas partie , le point idéal est quelque part au milieu.
Un test décisif des méthodes ou de la logique qui appartient à une classe et de ce qui appartient à l'extérieur consiste à examiner les dépendances que les méthodes introduiront. Les méthodes qui n'introduisent pas de dépendances supplémentaires sont très bien, tant qu'elles s'adaptent bien à l'abstraction de l'objet donné . Les méthodes qui nécessitent des dépendances externes supplémentaires (comme une bibliothèque de dessins ou une bibliothèque d'E / S) conviennent rarement. Même si vous faites disparaître les dépendances en utilisant l'injection de dépendances, je réfléchirais encore à deux fois si le placement de telles méthodes dans la classe de domaine est vraiment nécessaire.
Par conséquent, ni vous ne devez rendre chaque membre public, ni avoir à implémenter des méthodes pour chaque opération sur un objet à l'intérieur de la classe. Voici une suggestion alternative:
Désormais, l'
Cat
objet fournit suffisamment de logique pour permettre au code environnant de s'implémenter facilementprintInfo
etdraw
sans exposer tous les attributs en public. Le bon endroit pour ces deux méthodes n'est probablement pas une classe divineCatMethods
(carprintInfo
et cedraw
sont probablement des préoccupations différentes, donc je pense qu'il est très peu probable qu'elles appartiennent à la même classe).Je peux imaginer un
CatDrawingController
qui implémentedraw
(et utilise peut-être l'injection de dépendances pour obtenir un objet Canvas). Je peux également imaginer une autre classe qui implémente une sortie de console et utilisegetInfo
(printInfo
peut donc devenir obsolète dans ce contexte). Mais pour prendre des décisions judicieuses à ce sujet, il faut connaître le contexte et la façon dont laCat
classe sera réellement utilisée.C'est en fait la façon dont j'ai interprété les critiques du modèle de domaine anémique de Fowler - pour une logique généralement réutilisable (sans dépendances externes), les classes de domaine elles-mêmes sont un bon endroit, donc elles devraient être utilisées pour cela. Mais cela ne veut pas dire y mettre en œuvre une logique, bien au contraire.
Notez également que l'exemple ci-dessus laisse encore de la place pour prendre une décision concernant la (im) mutabilité. Si la
Cat
classe n'expose pas de setters, etImage
est immuable elle-même, cette conception permettra de rendreCat
immuable (ce que l'approche DTO ne fera pas). Mais si vous pensez que l'immuabilité n'est pas nécessaire ou inutile pour votre cas, vous pouvez également aller dans cette direction.la source
getInfo()
que cela pourrait induire en erreur quelqu'un qui pose cette question. Il mélange subtilement les responsabilités de présentationprintInfo()
, faisant ainsi échouer le but de laDrawingController
classeDrawingController
. Je suis d'accordgetInfo()
et ceprintInfo()
sont des noms horribles. ConsidéreztoString()
etprintTo(Stream stream)
Réponse tardive mais je ne résiste pas.
Dans la plupart des cas, la plupart des règles, appliquées sans réfléchir, vont pour la plupart tourner horriblement mal (y compris celle-ci).
Permettez-moi de vous raconter une histoire sur la naissance d'un objet au milieu du chaos d'un code procédural en bas à droite, rapide et sale, qui s'est produit, non pas par conception, mais par désespoir.
Mon stagiaire et moi programmons en binôme pour créer rapidement du code à jeter pour gratter une page Web. Nous n'avons absolument aucune raison de nous attendre à ce que ce code vive longtemps, alors nous ne faisons que cogner quelque chose qui fonctionne. Nous saisissons la page entière sous forme de chaîne et découpons les éléments dont nous avons besoin de la manière la plus étonnamment cassante que vous puissiez imaginer. Ne jugez pas. Ça marche.
Maintenant, en faisant cela, j'ai créé des méthodes statiques pour effectuer le découpage. Mon stagiaire a créé un cours DTO qui ressemblait beaucoup au vôtre
CatData
.Lorsque j'ai regardé le DTO pour la première fois, cela m'a dérangé. Les années de dommages que Java a causés à mon cerveau m'ont fait reculer dans les domaines publics. Mais nous travaillions en C #. C # n'a pas besoin de getters et setters prématurés pour préserver votre droit à rendre les données immuables ou encapsulées plus tard. Sans changer l'interface, vous pouvez les ajouter quand vous le souhaitez. Peut-être juste pour que vous puissiez définir un point d'arrêt. Le tout sans rien dire à vos clients. Oui C #. Boo Java.
J'ai donc tenu ma langue. J'ai regardé pendant qu'il utilisait mes méthodes statiques pour initialiser cette chose avant de l'utiliser. Nous en avions environ 14. C'était laid, mais nous n'avions aucune raison de nous en soucier.
Ensuite, nous en avions besoin ailleurs. Nous nous sommes retrouvés à vouloir copier et coller le code. 14 lignes d'initialisation sont lancées. Ça commençait à devenir douloureux. Il a hésité et m'a demandé des idées.
À contrecœur, je lui ai demandé: «considéreriez-vous un objet?
Il regarda son DTO et plissa le visage de confusion. "C'est un objet".
"Je veux dire un vrai objet"
"Hein?"
"Laissez-moi vous montrer quelque chose. Vous décidez si c'est utile"
J'ai choisi un nouveau nom et j'ai rapidement préparé quelque chose qui ressemblait un peu à ceci:
Cela n'a rien fait que les méthodes statiques ne faisaient pas déjà. Mais maintenant, j'avais aspiré les 14 méthodes statiques dans une classe où elles pouvaient être seules avec les données sur lesquelles elles travaillaient.
Je n'ai pas forcé mon stagiaire à l'utiliser. Je l'ai juste offert et je l'ai laissé décider s'il voulait s'en tenir aux méthodes statiques. Je suis rentré chez moi en pensant qu'il s'en tiendrait probablement à ce qu'il avait déjà travaillé. Le lendemain, j'ai découvert qu'il l'utilisait dans un tas d'endroits. Il a désencombré le reste du code qui était toujours laid et procédural mais ce peu de complexité nous était maintenant caché derrière un objet. C'était un peu mieux.
Maintenant, chaque fois que vous y accédez, cela fait un bon travail. Un DTO est une belle valeur en cache rapide. J'étais inquiet à ce sujet, mais j'ai réalisé que je pouvais ajouter la mise en cache si nous en avions besoin sans toucher au code d'utilisation. Je ne vais donc pas déranger avant que nous nous en soucions.
Suis-je en train de dire que vous devriez toujours vous en tenir aux objets OO sur les DTO? Non. Le DTO brille lorsque vous devez traverser une frontière qui vous empêche de bouger. Les DTO ont leur place.
Mais les objets OO aussi. Apprenez à utiliser les deux outils. Découvrez ce que chacun coûte. Apprenez à laisser le problème, la situation et le stagiaire décider. Le dogme n'est pas votre ami ici.
Étant donné que ma réponse est déjà ridiculement longue, permettez-moi de vous dissuader de certaines idées fausses en examinant votre code.
Où est ton constructeur? Cela ne me montre pas assez pour savoir si c'est utile.
Vous pouvez faire beaucoup de bêtises au nom du principe de responsabilité unique. Je pourrais dire que les cordes de chat et les cordes de chat devraient être séparées. Les méthodes de dessin et les images doivent toutes avoir leur propre classe. Que votre programme de course soit une responsabilité unique, vous ne devriez donc avoir qu'une seule classe. : P
Pour moi, la meilleure façon de suivre le principe de responsabilité unique est de trouver une bonne abstraction qui vous permet de mettre la complexité dans une boîte afin de pouvoir la cacher. Si vous pouvez lui donner un bon nom qui empêche les gens d'être surpris par ce qu'ils trouvent lorsqu'ils regardent à l'intérieur, vous l'avez assez bien suivi. S'attendre à ce qu'il dicte plus de décisions que cela pose problème. Honnêtement, vos deux listes de codes font cela, donc je ne vois pas pourquoi le SRP est important ici.
Et bien non. Le principe d'ouverture et de fermeture ne consiste pas à ajouter de nouvelles méthodes. Il s'agit de pouvoir changer l'implémentation des anciennes méthodes et de ne rien éditer. Rien qui vous utilise et pas vos anciennes méthodes. Au lieu de cela, vous écrivez du nouveau code ailleurs. Une certaine forme de polymorphisme le fera très bien. Ne voyez pas ça ici.
Eh bien, comment dois-je savoir? Regardez, le faire de toute façon a des avantages et des coûts. Lorsque vous séparez le code des données, vous pouvez modifier l'un ou l'autre sans avoir à recompiler l'autre. C'est peut-être extrêmement important pour vous. Peut-être que cela rend votre code inutilement compliqué.
Si cela vous fait vous sentir mieux, vous n'êtes pas si loin de ce que Martin Fowler appelle un objet paramètre . Vous n'êtes pas obligé de ne prendre que des primitives dans votre objet.
Ce que j'aimerais que vous fassiez, c'est de savoir comment faire votre séparation, ou non, dans l'un ou l'autre style de codage. Parce que croyez-le ou non, vous n'êtes pas obligé de choisir un style. Vous n'avez qu'à vivre avec votre choix.
la source
Vous êtes tombé sur un sujet de débat qui a suscité un débat parmi les développeurs depuis plus d'une décennie. En 2003, Martin Fowler a inventé l'expression «modèle de domaine anémique» (ADM) pour décrire cette séparation des données et des fonctionnalités. Lui - et d'autres qui sont d'accord avec lui - soutiennent que les «modèles de domaine riches» (mélange de données et de fonctionnalités) sont des «OO appropriés» et que l'approche ADM est un anti-modèle non OO.
Il y a toujours eu ceux qui rejettent cet argument et ce côté de l'argument est devenu plus fort et plus audacieux ces dernières années avec l'adoption par plus de développeurs de techniques de développement fonctionnel. Cette approche encourage activement la séparation des données et des préoccupations fonctionnelles. Les données doivent être immuables autant que possible, de sorte que l'encapsulation de l'état mutable devient une préoccupation non. Il n'y a aucun avantage à attacher des fonctions directement à ces données dans de telles situations. Que ce soit alors "pas OO" ou pas n'a absolument aucun intérêt pour ces gens.
Quel que soit le côté de la clôture sur lequel vous vous asseyez (je suis très fermement du côté "Martin Fowler parle d'une charge de vieux tosh" BTW), votre utilisation de méthodes statiques pour
printInfo
etdraw
est presque universellement mal vue. Il est difficile de se moquer des méthodes statiques lors de l'écriture de tests unitaires. Donc, s'ils ont des effets secondaires (tels que l'impression ou le dessin sur un écran ou un autre périphérique), ils ne doivent pas être statiques, ou doivent avoir l'emplacement de sortie transmis en tant que paramètre.Vous pourriez donc avoir une interface:
Et une implémentation qui est injectée dans le reste de votre système lors de l'exécution (avec d'autres implémentations utilisées pour les tests):
Ou ajoutez des paramètres supplémentaires pour supprimer les effets secondaires de ces méthodes:
la source
DTO - Objets de transport de données
sont utiles pour cela. Si vous allez shunter des données entre des programmes ou des systèmes, les DTO sont souhaitables car ils fournissent un sous-ensemble gérable d'un objet qui ne concerne que la structure et le formatage des données. L'avantage étant que vous n'avez pas à synchroniser les mises à jour des méthodes complexes sur plusieurs systèmes (tant que les données sous-jacentes ne changent pas).
L'intérêt de OO est de lier étroitement les données et le code agissant sur ces données. Séparer un objet logique en classes séparées est généralement une mauvaise idée.
la source
De nombreux modèles et principes de conception sont en conflit et, en fin de compte, seul votre jugement en tant que bon programmeur vous aidera à décider quels modèles doivent s'appliquer à un problème spécifique.
À mon avis, le principe Faire la chose la plus simple qui pourrait éventuellement fonctionner devrait gagner par défaut, sauf si vous avez une bonne raison de rendre la conception plus compliquée. Donc, dans votre exemple spécifique, j'opterais pour la première conception, qui est une approche orientée objet plus traditionnelle avec des données et des fonctions réunies dans la même classe.
Voici quelques questions que vous pourriez vous poser sur l'application:
Répondre à l'un de ces critères peut vous conduire à une conception plus complexe avec des classes DTO et fonctionnelles distinctes.
la source
Est-ce un bon schéma?
Vous allez probablement obtenir des réponses différentes ici parce que les gens suivent des écoles de pensée différentes. Donc, avant même de commencer: cette réponse est basée sur la programmation orientée objet telle qu'elle est décrite par Robert C. Martin dans le livre "Clean Code".
"True" OOP
Pour autant que je sache, il y a 2 interprétations de la POO. Pour la plupart des gens, cela se résume à "un objet est une classe".
Cependant, j'ai trouvé l'autre école de pensée plus utile: "Un objet est quelque chose qui fait quelque chose". Si vous travaillez avec des classes, chaque classe serait l'une des deux choses: un " détenteur de données " ou un objet . Les détenteurs de données détiennent des données (duh), les objets font des choses. Cela ne signifie pas qu'un détenteur de données ne peut pas avoir de méthodes! Pensez à a
list
: Alist
ne fait rien, mais il a des méthodes pour appliquer la façon dont il détient les données .Détenteurs de données
Vous
Cat
est un excellent exemple de détenteur de données. Bien sûr, en dehors de votre programme, un chat est quelque chose qui fait quelque chose , mais à l'intérieur de votre programme, aCat
est une chaînename
, un intweight
et une imageimage
.Que les détenteurs de données ne fassent rien ne signifie pas non plus qu'ils ne contiennent pas de logique métier! Si votre
Cat
classe a un constructeur nécessitantname
,weight
etimage
, vous avez réussi à encapsuler la règle métier que chaque chat en a. Si vous pouvez modifierweight
après laCat
création d' une classe, c'est une autre règle métier encapsulée. Ce sont des choses très basiques, mais cela signifie simplement qu'il est très important de ne pas se tromper - et de cette façon, vous vous assurez qu'il est impossible de se tromper.Objets
Les objets font quelque chose. Si vous voulez des objets Clean *, nous pouvons changer cela en "Les objets ne font qu'une chose".
L'impression des informations sur le chat est le travail d'un objet. Par exemple, vous pouvez appeler cet objet "
CatInfoLogger
", avec une méthode publiqueLog(Cat)
. C'est tout ce que nous devons savoir de l'extérieur: cet objet enregistre les informations d'un chat et pour ce faire, il a besoin d'unCat
.À l'intérieur, cet objet aurait des références à tout ce dont il a besoin pour remplir sa seule responsabilité de l'exploitation des chats. Dans ce cas, ce n'est qu'une référence à
System
forSystem.out.println
, mais dans la plupart des cas, ce seront des références privées à d'autres objets. Si la logique de formatage de la sortie avant de l'imprimer devient trop complexe,CatInfoLogger
il suffit d'obtenir une référence à un nouvel objetCatInfoFormatter
. Si plus tard, chaque journal doit également être écrit dans un fichier, vous pouvez ajouter unCatToFileLogger
objet qui le fait.Détenteurs de données vs objets - résumé
Maintenant, pourquoi feriez-vous tout cela? Parce que maintenant changer une classe ne change qu'une chose ( la façon dont un chat est connecté dans l'exemple). Si vous changez un objet, vous changez seulement la façon dont une certaine action est effectuée; si vous changez de détenteur de données, vous ne modifiez que les données détenues et / ou de quelle manière elles sont détenues.
La modification des données peut également signifier que la façon dont quelque chose est fait doit changer, mais ce changement ne se produira que lorsque vous le réaliserez vous-même en modifiant l'objet responsable.
Trop fort?
Cette solution peut sembler un peu exagérée. Permettez-moi d'être franc: c'est le cas . Si tout votre programme est aussi grand que l'exemple, ne faites rien de ce qui précède - choisissez simplement l'une des façons proposées avec un tirage au sort. Il n'y a pas de risque de confusion autre code s'il est pas d' autre code.
Cependant, tout logiciel "sérieux" (= plus qu'un script ou 2) est probablement suffisamment gros pour bénéficier d'une telle structure.
Réponse
Transformer "la plupart des classes" (sans autre qualification) en DTO / détenteurs de données, n'est pas un anti-modèle, car ce n'est en fait pas un modèle - c'est plus ou moins aléatoire.
Cependant, la séparation des données et des objets est considérée comme un bon moyen de garder votre code propre *. Parfois, vous n'aurez besoin que d'un DTO plat et "anémique" et c'est tout. D'autres fois, vous avez besoin de méthodes pour appliquer la manière dont les données sont conservées. Ne mettez tout simplement pas la fonctionnalité de votre programme avec les données.
* C'est "Clean" avec un C majuscule comme le titre du livre, car - rappelez-vous le premier paragraphe - il s'agit d'une école de pensée, alors qu'il y en a aussi d'autres.
la source