Quelle est la vraie responsabilité d'une classe?

42

Je me demande sans cesse s'il est légitime d'utiliser des verbes basés sur des noms dans la programmation orientée objet.
Je suis tombé sur ce brillant article , bien que je sois toujours en désaccord avec ce qu'il dit.

Pour expliquer un peu plus le problème, l'article stipule qu'il ne devrait pas exister de FileWriterclasse, mais que l'écriture est une action, elle doit être une méthode de la classe File. Vous vous rendrez compte que cela dépend souvent du langage puisqu'un programmeur Ruby serait probablement contre l'utilisation d'une FileWriterclasse (Ruby utilise la méthode File.openpour accéder à un fichier), contrairement à un programmeur Java.

Mon point de vue personnel (et oui, très humble) est que cela violerait le principe de responsabilité unique. Quand je programmais en PHP (parce que PHP est évidemment le meilleur langage pour la programmation orientée objet, non?), J'utilisais souvent ce type de framework:

<?php

// This is just an example that I just made on the fly, may contain errors

class User extends Record {

    protected $name;

    public function __construct($name) {
        $this->name = $name;
    }

}

class UserDataHandler extends DataHandler /* knows the pdo object */ {

    public function find($id) {
         $query = $this->db->prepare('SELECT ' . $this->getFields . ' FROM users WHERE id = :id');
         $query->bindParam(':id', $id, PDO::PARAM_INT);
         $query->setFetchMode( PDO::FETCH_CLASS, 'user');
         $query->execute();
         return $query->fetch( PDO::FETCH_CLASS );
    }


}

?>

Je crois comprendre que le suffixe DataHandler n’ajoute rien de pertinent ; mais le fait est que le principe de responsabilité unique nous dicte qu'un objet utilisé en tant que modèle contenant des données (peut-être appelé un enregistrement) ne devrait pas également avoir la responsabilité d'effectuer des requêtes SQL et un accès à la base de données. Cela invalide en quelque sorte le modèle ActionRecord utilisé par exemple par Ruby on Rails.

L’autre jour, j’ai rencontré ce code C # (yay, quatrième langage objet utilisé dans ce post):

byte[] bytes = Encoding.Default.GetBytes(myString);
myString = Encoding.UTF8.GetString(bytes);

Et je dois dire que cela n'a pas beaucoup de sens pour moi qu'une classe Encodingou une Charsetclasse encode réellement des chaînes. Cela devrait simplement être une représentation de ce qu'est réellement un encodage.

Ainsi, j'aurais tendance à penser que:

  • FileOuvrir, lire ou sauvegarder des fichiers n’est pas une responsabilité de classe.
  • Ce n'est pas une Xmlresponsabilité de classe de se sérialiser.
  • UserInterroger une base de données n’est pas une responsabilité de classe.
  • etc.

Cependant, si nous extrapolons ces idées, pourquoi aurions- Objectnous un toStringcours? Ce n'est pas la responsabilité d'une voiture ou d'un chien de se convertir en chaîne, n'est-ce pas?

Je comprends que d’un point de vue pragmatique, se débarrasser de la toStringméthode pour conserver la beauté d’une forme SOLIDE stricte, qui rend le code plus facile à gérer en le rendant inutile, n’est pas une option acceptable.

Je comprends aussi qu’il n’existe peut-être pas de réponse exacte (ce qui constituerait davantage une dissertation qu’une réponse sérieuse), ou qu’elle pourrait être fondée sur une opinion. Néanmoins, j'aimerais quand même savoir si mon approche suit réellement le principe de la responsabilité unique.

Quelle est la responsabilité d'une classe?

Pierre Arlaud
la source
Les objets toString sont principalement pratiques, et toString est généralement utilisé pour afficher rapidement l'état interne pendant le débogage
fratchet freak
3
@ratchetfreak Je suis d'accord, mais c'était un exemple (presque une blague) pour montrer que la responsabilité unique est très souvent rompue dans la manière que j'ai décrite.
Pierre Arlaud
8
Alors que le principe de responsabilité unique a ses partisans ardents, il s’agit, à mon avis, au mieux d’une ligne directrice, et d’une note très vague pour la très grande majorité des choix de conception. Le problème avec SRP est que vous vous retrouvez avec des centaines de classes avec des noms étranges, il est donc très difficile de trouver ce dont vous avez besoin (ou de savoir si ce dont vous avez déjà besoin) et de saisir la situation dans son ensemble. Je préfère de loin avoir moins de classes bien nommées qui me disent instantanément à quoi s'attendre, même si elles ont plusieurs responsabilités. Si à l'avenir les choses changent, je diviserai la classe.
Dunk

Réponses:

24

Compte tenu de certaines divergences entre les langues, le sujet peut être délicat. Ainsi, je formule les commentaires suivants d’une manière qui tente d’être aussi complète que possible dans le domaine de OO.

Tout d'abord, le "principe de responsabilité unique" est un réflexe - explicitement déclaré - du concept de cohésion . En lisant la littérature de l'époque (vers 1970), les gens peinaient (et ont toujours) du mal à définir ce qu'est un module et à savoir comment le construire de manière à préserver les belles propriétés. Ainsi, ils diraient "voici un tas de structures et de procédures, je vais en faire un module", mais sans critère expliquant pourquoi cet ensemble d'éléments arbitraires sont regroupés, l'organisation risque de ne pas avoir de sens - peu de "cohésion". Des discussions sur les critères ont donc émergé.

La première chose à noter ici est que, jusqu’à présent, le débat porte sur l’organisation et les effets connexes sur la maintenance et la compréhensibilité (un ordinateur n’a guère d’importance si un module «a du sens»).

Ensuite, quelqu'un d'autre (M. Martin) est venu et a appliqué la même pensée à l'unité d'une classe en tant que critère à utiliser pour penser à ce qui devrait ou non lui appartenir, en faisant de ce critère un principe, celui en discussion ici. Le point qu'il a fait valoir était qu ' "une classe ne devrait avoir qu'une seule raison de changer" .

Nous savons d'expérience que beaucoup d'objets (et de nombreuses classes) qui semblent faire «beaucoup de choses» ont une très bonne raison de le faire. Le cas indésirable serait les classes surchargées de fonctionnalités au point d’être impénétrables à la maintenance, etc. Et comprendre cette dernière, c’est voir où m. Martin visait quand il a développé le sujet.

Bien sûr, après avoir lu ce que m. Martin a écrit, il devrait être clair que ce sont des critères pour la direction et la conception afin d' éviter les scénarios problématiques, et non en aucune manière de rechercher une quelconque conformité, et encore moins une stricte conformité, spécialement lorsque la "responsabilité" est mal définie (et que des questions telles que viole le principe? "sont des exemples parfaits de la confusion généralisée). Ainsi, je trouve malheureux que cela s'appelle un principe, trompant les gens dans essayer de le prendre aux dernières conséquences, où il ne ferait aucun bien. M. Martin a lui-même évoqué des concepts qui "font plus d'une chose" et qui devraient probablement être conservés de cette façon, car la séparation produirait de pires résultats. De plus, il y a beaucoup de défis connus concernant la modularité (et ce sujet en est un exemple), nous ne sommes pas sur le point d'avoir de bonnes réponses, même pour quelques questions simples à ce sujet.

Cependant, si nous extrapolons ces idées, pourquoi Object aurait-il une classe toString? Ce n'est pas la responsabilité d'une voiture ou d'un chien de se convertir en chaîne, n'est-ce pas?

Permettez-moi maintenant de faire une pause pour dire quelque chose ici toString: il y a une chose fondamentale généralement négligée lorsque l'on fait la transition de la pensée de modules en classes et que l'on réfléchit aux méthodes qui devraient appartenir à une classe. Et la chose est la répartition dynamique (aka, liaison tardive, "polymorphisme").

Dans un monde sans "méthodes dominantes", choisir entre "obj.toString ()" ou "toString (obj)" dépend uniquement de la préférence de syntaxe. Cependant, dans un monde où les programmeurs peuvent modifier le comportement d'un programme en ajoutant une sous-classe avec la mise en œuvre distincte d'une méthode existante / remplacée, ce choix n'a plus de goût: faire d'une procédure une méthode peut également en faire un candidat à la substitution, Il en va peut-être de même pour les "procédures libres" (les langages prenant en charge les méthodes multiples ont un moyen de sortir de cette dichotomie). Par conséquent, il ne s’agit plus seulement d’une discussion sur l’organisation, mais également sur la sémantique. Enfin, la classe à laquelle la méthode est liée devient également une décision d’impact (et dans de nombreux cas, jusqu’à présent, nous n’avons guère plus que des lignes directrices pour nous aider à décider où les choses appartiennent,

Enfin, nous sommes confrontés à des langages qui impliquent des décisions de conception terribles, obligeant par exemple à créer une classe pour chaque petite chose. Ainsi, ce qui était autrefois la raison canonique et les critères principaux pour avoir des objets (et dans la classe-land, donc, des classes), c'est-à-dire avoir ces "objets" qui sont en quelque sorte des "comportements qui se comportent également comme des données" , mais protègent à tout prix leur représentation concrète (le cas échéant) des manipulations directes (et c’est le principal indice de ce que devrait être l’interface d’un objet, du point de vue de ses clients), devient floue et confuse.

Thiago Silva
la source
31

[Note: Je vais parler d'objets ici. La programmation orientée objet est, après tout, l'objet, pas les classes.]

La responsabilité d'un objet dépend principalement de votre modèle de domaine. Il existe généralement de nombreuses façons de modéliser le même domaine, et vous choisirez l'une ou l'autre méthode en fonction de l'utilisation du système.

Comme nous le savons tous, la méthodologie "verbe / nom" souvent enseignée dans les cours d'introduction est ridicule, car elle dépend tellement de la manière dont vous formulez une phrase. Vous pouvez exprimer presque n'importe quoi sous forme de nom ou de verbe, d'une voix active ou passive. Si votre modèle de domaine dépend de ce qui est erroné, il doit s'agir d'un choix de conception conscient, qu'il s'agisse ou non d'un objet ou d'une méthode, et non d'une simple conséquence accidentelle de la façon dont vous formulez les exigences.

Cependant, il ne montre que, comme vous avez de nombreuses possibilités d'exprimer la même chose en anglais, vous avez beaucoup de possibilités d'exprimer la même chose dans votre modèle de domaine ... et aucune de ces possibilités est par nature plus correcte que les autres. Ça dépend du contexte.

Voici un exemple qui est également très populaire dans les cours d'introduction à l'OO: un compte bancaire. Ce qui est typiquement modélisé comme un objet avec un balancechamp et une transferméthode. En d'autres termes: le solde du compte est constitué de données et un transfert est une opération . Ce qui est un moyen raisonnable de modéliser un compte bancaire.

Sauf que ce n'est pas ainsi que les comptes bancaires sont modélisés dans un logiciel bancaire réel (et en fait, ce n'est pas ainsi que les banques fonctionnent dans le monde réel). Au lieu de cela, vous avez un bordereau de transaction et le solde du compte est calculé en ajoutant (et en soustrayant) tous les bordereaux de transaction d'un compte. En d'autres termes: le transfert est une donnée et la balance est une opération ! (Il est intéressant de noter que cela rend également votre système purement fonctionnel, puisque vous n’avez jamais besoin de modifier le solde, vos objets de compte et les objets de transaction ne doivent jamais être modifiés, ils sont immuables.)

En ce qui concerne votre question spécifique sur toString, je suis d'accord. Je préfère de loin la solution Haskell d’une Showable classe de types . (Scala nous apprend que les classes de types s’accordent parfaitement avec OO.) Même chose avec l’égalité. L'égalité n'est très souvent pas une propriété d'un objet mais du contexte dans lequel l'objet est utilisé. Il suffit de penser aux nombres en virgule flottante: que doit être l’epsilon? Encore une fois, Haskell a la Eqclasse de type.

Jörg W Mittag
la source
J'aime vos comparaisons entre haskell, ça donne un peu plus la réponse… fraîche. Que diriez-vous alors des conceptions qui permettent ActionRecordà la fois d'être un modèle et un demandeur de base de données? Peu importe le contexte, il semble enfreindre le principe de responsabilité unique.
Pierre Arlaud
5
"... parce que cela dépend tellement de la façon dont vous formulez une phrase." Yay! Oh, et j'adore votre exemple bancaire. Je ne savais jamais que dans les années quatre-vingt, on m'avait déjà enseigné la programmation fonctionnelle :) un fait nouveau) ...
Marjan Venema
1
Je ne dirais pas que la méthodologie Verb / Noun est "ridicule". Je dirais que les conceptions simplistes échoueront et qu'il est très difficile de bien faire les choses . Parce que, contre-exemple, une API RESTful n'est pas une méthodologie Verb / Noun? Où, pour un degré de difficulté supplémentaire, les verbes sont extrêmement limités?
user949300
@ user949300: Le fait que l'ensemble des verbes soit corrigé évite précisément le problème que j'ai mentionné, à savoir que vous pouvez formuler des phrases de multiples façons, rendant ainsi le nom et le verbe dépend du style d'écriture et non de l'analyse de domaine. .
Jörg W Mittag
8

Trop souvent, le principe de responsabilité unique devient un principe de responsabilité zéro, et vous vous retrouvez avec des classes anémiques qui ne font rien (à l'exception des setters et des getters), ce qui conduit au désastre du royaume des noms .

Vous ne l'obtiendrez jamais parfait, mais il vaut mieux qu'un cours en fasse un peu trop que trop peu.

Dans votre exemple avec Encoding, IMO, il devrait certainement être capable d’encoder. Que pensez-vous qu'il devrait faire à la place? Juste avoir un nom "utf" est zéro responsabilité. Maintenant, peut-être que le nom devrait être Encoder. Mais, comme le disait Konrad, les données (quel encodage) et le comportement (le faire) vont de pair .

utilisateur949300
la source
7

Une instance d'une classe est une fermeture. C'est ça. Si vous pensez que de cette façon, tous les logiciels bien conçus que vous regardez auront l’air exact, et que tous les logiciels mal conçus ne le seront pas. Laisse moi développer.

Si vous voulez écrire quelque chose à écrire dans un fichier, pensez à tout ce que vous devez spécifier (dans le système d'exploitation): nom du fichier, méthode d'accès (lecture, écriture, ajout), la chaîne que vous voulez écrire.

Vous construisez donc un objet File avec le nom de fichier (et la méthode d’accès). L'objet File est maintenant fermé sur le nom du fichier (dans ce cas, il l'aura probablement pris comme valeur readonly / const). L'instance File est maintenant prête à recevoir des appels de la méthode "write" définie dans la classe. Cela prend juste un argument de chaîne, mais dans le corps de la méthode write, l'implémentation, il est également possible d'accéder au nom de fichier (ou au descripteur de fichier créé à partir de celui-ci).

Une instance de la classe File classe donc certaines données dans une sorte de blob composite, de sorte que son utilisation ultérieure est plus simple. Lorsque vous avez un objet File, vous n'avez pas à vous soucier de ce qu'est le nom de fichier, vous ne vous souciez que de la chaîne que vous avez mise en argument dans l'appel en écriture. Pour réitérer - l'objet File encapsule tout ce que vous n'avez pas besoin de savoir sur l'endroit où la chaîne que vous voulez écrire va réellement.

La plupart des problèmes que vous souhaitez résoudre sont regroupés de la manière suivante: éléments créés au début, éléments créés au début de chaque itération d'une sorte de boucle de processus, puis éléments différents déclarés dans les deux moitiés d'un if, les éléments créés à le début d’une sorte de sous-boucle, puis des éléments déclarés comme faisant partie de l’algorithme dans cette boucle, etc. Au fur et à mesure que vous ajoutez de plus en plus d’appels de fonctions à la pile, vous produisez un code de plus en plus fin, mais vous aurez à chaque fois regroupé les données dans les couches situées au bas de la pile en une sorte d’abstraction agréable facilitant l’accès. Voyez ce que vous faites, vous utilisez "OO" comme une sorte de cadre de gestion de fermeture pour une approche de programmation fonctionnelle.

La solution de raccourci à certains problèmes implique l’état mutable des objets, ce qui n’est pas si fonctionnel: vous manipulez les fermetures de l’extérieur. Certaines des choses de votre classe sont "globales", du moins dans l’optique ci-dessus. Chaque fois que vous écrivez un compositeur - essayez d'éviter ceux-ci. Je dirais que c'est là que la responsabilité de votre classe, ou des autres classes dans lesquelles elle fonctionne, est erronée. Mais ne vous inquiétez pas trop - il est parfois pragmatique de mettre en place un état modulable pour résoudre certains types de problèmes rapidement, sans charge cognitive excessive, et pour que rien ne soit réellement fait.

En résumé, pour répondre à votre question, quelle est la véritable responsabilité d’une classe? Il s'agit de présenter une sorte de plate-forme, basée sur certaines données sous-jacentes, pour obtenir un type d'opération plus détaillé. Il s'agit d'encapsuler la moitié des données impliquées dans une opération qui change moins souvent que l'autre moitié des données (pensez au currying ...). Par exemple, nom de fichier vs chaîne à écrire.

Souvent, ces encapsulations ressemblent superficiellement à un objet du monde réel / à un objet du domaine du problème, mais ne vous laissez pas berner. Lorsque vous créez une classe de voiture, ce n'est pas une voiture, c'est une collection de données qui constitue une plate-forme sur laquelle vous pouvez réaliser certaines tâches que vous pourriez envisager de faire sur une voiture. La formation d'une représentation sous forme de chaîne (toString) est l'une de ces choses: cracher toutes les données internes. N'oubliez pas que, parfois, une classe de voitures peut ne pas être la bonne classe, même si le problème est entièrement lié aux voitures. Contrairement à Kingdom of nouns, ce sont les opérations, les verbes sur lesquels une classe devrait être basée.

Benoît
la source
4

Je comprends aussi qu’il n’existe peut-être pas de réponse exacte (ce qui constituerait plus une dissertation qu’une réponse sérieuse) à cette question, ou que cela puisse être fondé sur une opinion. Néanmoins, j'aimerais quand même savoir si mon approche suit réellement le principe de la responsabilité unique.

Bien que ce soit le cas, cela ne donnera pas nécessairement un bon code. Au-delà du simple fait que toute règle suivie aveuglément mène à un code incorrect, vous partez d'un principe non valide: les objets (en programmation) ne sont pas des objets (physiques).

Les objets sont un ensemble cohérent de données et d'opérations (et parfois seulement l'un des deux). Bien que ceux-ci modélisent souvent des objets du monde réel, la différence entre un modèle informatique de quelque chose et cette chose même nécessite des différences.

En prenant cette ligne de démarcation entre un "nom" qui représente une chose et d'autres choses qui la consomment, vous allez à l'encontre d'un avantage clé de la programmation orientée objet (avoir une fonction et un état ensemble pour que la fonction puisse protéger les invariants de l'état) . Pire encore, vous essayez de représenter le monde physique dans un code qui, comme l’a montré l’histoire, ne fonctionnera pas bien.

Telastyn
la source
4

L’autre jour, j’ai rencontré ce code C # (yay, quatrième langage objet utilisé dans ce post):

byte[] bytes = Encoding.Default.GetBytes(myString);
myString = Encoding.UTF8.GetString(bytes);

Et je dois dire que cela n'a pas beaucoup de sens pour moi qu'une classe Encodingou une Charsetclasse encode réellement des chaînes. Cela devrait simplement être une représentation de ce qu'est réellement un encodage.

En théorie, oui, mais je pense que C # fait un compromis justifié dans un souci de simplicité (par opposition à l'approche plus stricte de Java).

Si vous Encodingne représentez que (ou n'identifiez) pas un certain codage - disons UTF-8 -, vous aurez évidemment aussi besoin d'une Encoderclasse pour pouvoir l'implémenter GetBytes- mais vous devrez alors gérer la relation entre Encoders et Encodings. se retrouver avec notre bon vieux

EncodersFactory.getDefaultFactory().createEncoder(new UTF8Encoding()).getBytes(myString)

si brillamment loufoque dans cet article que vous avez lié (bonne lecture, en passant).

Si je vous comprends bien, vous demandez où est la limite.

De l’autre côté du spectre se trouve l’approche procédurale, qui n’est pas difficile à mettre en œuvre dans les langages POO avec l’utilisation de classes statiques:

HelpfulStuff.GetUTF8Bytes(myString)

Le gros problème, c’est que lorsque l’auteur de la HelpfulStufffeuille et moi-même me retrouvons assis devant le moniteur, je n’ai aucun moyen de savoir où GetUTF8Bytesest implémenté.

Est - ce que je cherche dans HelpfulStuff, Utils, StringHelper?

Seigneur, aidez-moi si la méthode est mise en œuvre de manière indépendante dans les trois cas ... ce qui arrive souvent, il suffit que le gars avant moi ne sache pas où chercher cette fonction non plus, et pense qu'il n'y en avait pas pourtant ils en ont sorti un autre et maintenant nous les avons en abondance.

Pour moi, une conception correcte ne consiste pas à satisfaire des critères abstraits, mais à mon aptitude à continuer une fois que je suis assis devant votre code. Maintenant, comment

EncodersFactory.getDefaultFactory().createEncoder(new UTF8Encoding()).getBytes(myString)

taux dans cet aspect? Mal aussi. Ce n'est pas une réponse que je veux entendre quand je crie à mon collègue "comment encoder une chaîne dans une séquence d'octets?" :)

J'adopterais donc une approche modérée et une approche libérale en matière de responsabilité unique. Je préférerais que la Encodingclasse identifie un type de codage et fasse le codage réel: données non séparées du comportement.

Je pense que ça passe comme un motif de façade, ce qui aide à graisser les engrenages. Ce modèle légitime viole-t-il SOLID? Une question connexe: le modèle de façade viole-t-il le SRP?

Notez que cela pourrait être la façon dont l'obtention des octets codés est implémentée en interne (dans les bibliothèques .NET). Les classes ne sont tout simplement pas visibles publiquement et seule cette API "facade" est exposée à l'extérieur. Ce n'est pas si difficile de vérifier si c'est vrai, je suis un peu trop paresseux pour le faire maintenant :) Ce n'est probablement pas le cas, mais cela n'invalide pas mon propos.

Vous pouvez choisir de simplifier la mise en œuvre visible par le public pour des raisons de convivialité tout en maintenant une mise en œuvre canonique plus sévère en interne.

Konrad Morawski
la source
1

En Java, l'abstraction utilisée pour représenter les fichiers est un flux, certains flux sont unidirectionnels (en lecture seule ou en écriture uniquement), d'autres sont bidirectionnels. Pour Ruby, la classe File est l'abstraction qui représente les fichiers du système d'exploitation. La question devient alors quelle est sa seule responsabilité? En Java, la responsabilité de FileWriter est de fournir un flux d'octets unidirectionnel connecté à un fichier de système d'exploitation. Dans Ruby, la responsabilité de File est de fournir un accès bidirectionnel à un fichier système. Ils remplissent tous les deux le PÉR, ils ont juste des responsabilités différentes.

Ce n'est pas la responsabilité d'une voiture ou d'un chien de se convertir en chaîne, n'est-ce pas?

Bien sûr, pourquoi pas? Je m'attends à ce que la classe Car représente pleinement une abstraction de voiture. S'il est logique que l'abstraction de voiture soit utilisée lorsqu'une chaîne est nécessaire, je m'attends parfaitement à ce que la classe Car prenne en charge la conversion en chaîne.

Pour répondre directement à la question plus large, la responsabilité d'une classe est d'agir comme une abstraction. Ce travail peut être complexe, mais c'est un peu le point, une abstraction devrait cacher des choses complexes derrière une interface plus facile à utiliser, moins complexe. Le PRS étant un guide de conception, concentrez-vous sur la question "que représente cette abstraction?". Si plusieurs comportements différents sont nécessaires pour résumer complètement le concept, qu’il en soit ainsi.

pierre précieuse
la source
0

La classe peut avoir plusieurs responsabilités. Au moins dans la POO classique centrée sur les données.

Si vous appliquez pleinement le principe de responsabilité unique, vous obtenez une conception centrée sur la responsabilité dans laquelle chaque méthode non assistante appartient à sa propre classe.

Je pense qu'il n'y a rien de mal à extraire un élément de complexité d'une classe de base, comme dans le cas de File et FileWriter. L'avantage est que vous obtenez une séparation claire du code responsable d'une opération donnée et que vous pouvez remplacer (sous-classe) uniquement ce morceau de code au lieu de remplacer toute la classe de base (par exemple, Fichier). Néanmoins, l’application systématique me semble exagérée. Vous devez simplement gérer beaucoup plus de classes et pour chaque opération, vous devez appeler sa classe respective. C'est une flexibilité qui a un coût.

Ces classes extraites doivent avoir des noms très descriptifs basés sur l'opération qu'ils contiennent, par exemple <X>Writer, <X>Reader, <X>Builder. Des noms tels que <X>Manager, <X>Handlerne décrivent pas du tout l'opération et s'ils s'appellent ainsi parce qu'ils contiennent plus d'une opération, on ne sait pas exactement ce que vous avez réalisé. Vous avez séparé la fonctionnalité de ses données, vous enfreignez toujours le principe de responsabilité unique, même dans cette classe extraite, et si vous recherchez une méthode, vous ne savez peut-être pas où la chercher (si elle est <X>ou <X>Manager).

Maintenant, votre cas avec UserDataHandler est différent car il n'y a pas de classe de base (la classe qui contient les données réelles). Les données de cette classe sont stockées dans une ressource externe (base de données) et vous utilisez une API pour y accéder et les manipuler. Votre classe User représente un utilisateur d'exécution, ce qui est vraiment quelque chose de différent d'un utilisateur persistant et la séparation des responsabilités (préoccupations) peut s'avérer utile, en particulier si vous avez beaucoup de logique liée à l'utilisateur d'exécution.

Vous avez probablement appelé UserDataHandler comme cela parce qu'il n'y a fondamentalement que des fonctionnalités dans cette classe et pas de données réelles. Cependant, vous pouvez également résumer ce fait et considérer que les données de la base de données appartiennent à la classe. Avec cette abstraction, vous pouvez nommer la classe en fonction de ce qu’elle est et non de ce qu’elle fait. Vous pouvez lui donner un nom tel que UserRepository, qui est plutôt astucieux et suggère également l’utilisation du modèle de référentiel .

climat
la source