J'ai d'énormes classes de plus de 2k lignes de code (et en pleine croissance) que je voudrais refactoriser si possible, pour avoir un design plus léger et plus propre.
La raison de sa taille est principalement due au fait que ces classes gèrent un ensemble de cartes auquel la plupart des méthodes doivent accéder et que les méthodes sont très connectées les unes aux autres.
Je vais donner un exemple très concret: j'ai une classe appelée Server
qui gère les messages entrants. Il a des méthodes comme joinChatroom
, searchUsers
, sendPrivateMessage
, etc. Toutes ces méthodes manipuler les cartes telles que users
, chatrooms
, servers
, ...
Peut-être que ce serait bien si je pouvais avoir une classe qui gère les messages concernant les Chatrooms, une autre qui traite des utilisateurs, etc. mais le problème principal ici est que je dois utiliser toutes les cartes dans la plupart des méthodes. C'est pourquoi pour l'instant, ils sont tous collés dans la Server
classe car ils s'appuient tous sur ces cartes communes et les méthodes sont très liées les unes aux autres.
J'aurais besoin de créer une classe Chatrooms, mais avec une référence à chacun des autres objets. Une classe utilise à nouveau une référence à tous les autres objets, etc.
J'ai l'impression que je ferais quelque chose de mal.
la source
Réponses:
D'après votre description, je suppose que vos cartes ne sont que des sacs de données, avec toute la logique des
Server
méthodes. En poussant toute la logique du chat dans une classe distincte, vous êtes toujours coincé avec des cartes contenant des données.Essayez plutôt de modéliser des salons de discussion, des utilisateurs, etc. en tant qu'objets. De cette façon, vous ne passerez que des objets spécifiques requis pour une certaine méthode, au lieu d'énormes cartes de données.
Par exemple:
Il est maintenant facile d'appeler quelques méthodes spécifiques pour gérer les messages:
Vous souhaitez rejoindre un salon de discussion?
Vous souhaitez envoyer un message privé?
Vous souhaitez envoyer un message public?
la source
Vous devriez pouvoir créer une classe contenant chaque collection. Bien qu'il
Server
aura besoin d'une référence à chacune de ces collections, il n'a besoin que de la quantité minimale de logique qui n'impliquera pas l'accès ou la maintenance des collections sous-jacentes. Cela rendra plus évidente exactement ce que fait le serveur et séparera comment il le fait.la source
Quand j'ai vu de grandes classes comme celle-ci, j'ai constaté qu'il y avait souvent une classe (ou plus) à essayer de sortir. Si vous connaissez une méthode qui, selon vous, pourrait ne pas être liée à cette classe, rendez-la statique. Le compilateur vous indiquera ensuite d'autres méthodes que cette méthd appelle. Java insistera également sur le fait qu'ils sont statiques. Vous les rendez statiques. Encore une fois, le compilateur vous indiquera toute méthode qu'il appelle. Vous continuez à le faire encore et encore jusqu'à ce que vous n'ayez plus d'échecs de compilation. Ensuite, vous avez un tas de méthodes statiques dans votre grande classe. Vous pouvez maintenant les extraire dans une nouvelle classe et rendre la méthode non statique. Vous pouvez ensuite appeler cette nouvelle classe à partir de votre grande classe d'origine (qui devrait maintenant contenir moins de lignes)
Vous pouvez ensuite répéter le processus jusqu'à ce que vous soyez satisfait de la conception de la classe.
Le livre de Martin Fowler est une très bonne lecture, donc je le recommanderais aussi car il y a des moments où vous ne pouvez pas utiliser cette astuce statique.
la source
Étant donné que la plupart de votre code existe, je suggérerais d'utiliser des classes d'assistance pour déplacer vos méthodes. Cela facilitera la refactorisation. Ainsi, votre classe de serveur contiendra toujours les cartes. Mais il utilise une classe d'assistance, comme ChatroomHelper avec des méthodes telles que join (Map chatrooms, String user), List getUsers (Map chatrooms), Map getChatrooms (String user).
La classe serveur contiendra une instance de ChatroomHelper, UserHelper, etc., déplaçant ainsi les méthodes vers ses classes d'assistance logique. Avec cela, vous pouvez laisser les méthodes publiques du serveur intactes, de sorte que tout appelant n'a pas besoin de changer.
la source
Pour ajouter à la réponse perspicace de casablanca - si plusieurs classes doivent faire les mêmes choses de base avec un certain type d'entité (ajouter des utilisateurs à une collection, gérer les messages, etc.), ces processus doivent également être séparés.
Il existe plusieurs façons de le faire - par héritage ou par composition. Pour l'héritage, vous pouvez utiliser des classes de base abstraites avec des méthodes concrètes qui fournissent les champs et les fonctionnalités pour gérer les utilisateurs ou les messages par exemple, et avoir des entités comme
chatroom
etuser
(ou toute autre) étendre ces classes.Pour diverses raisons , c'est une bonne règle générale d'utiliser la composition plutôt que l'héritage. Vous pouvez utiliser la composition pour le faire de différentes manières. Étant donné que la gestion des utilisateurs ou des messages est une fonction centrale de la responsabilité des classes, on peut soutenir que l'injection de constructeur est la plus appropriée. De cette façon, la dépendance est transparente et un objet ne peut pas être créé sans avoir la fonctionnalité requise. Si la façon dont les utilisateurs ou les messages sont traités est susceptible de changer ou d'être étendue, vous devriez envisager d'utiliser quelque chose comme le modèle de stratégie .
Dans les deux cas, assurez-vous de coder vers des interfaces, pas des classes concrètes pour plus de flexibilité.
Cela étant dit, tenez toujours compte du coût de la complexité supplémentaire lors de l'utilisation de tels modèles - si vous n'en avez pas besoin, ne le codez pas. Si vous savez que vous n'allez probablement pas changer la façon dont la gestion des utilisateurs / messages est effectuée, vous n'avez pas besoin de la complexité structurelle d'un modèle de stratégie - mais pour séparer les préoccupations et éviter la répétition, vous devez toujours divorcer des fonctionnalités communes à partir des instances concrètes qui l'utilisent - et, s'il n'existe aucune raison impérieuse du contraire, composez les utilisateurs de ces fonctionnalités de gestion (chatrooms, utilisateurs) avec un objet qui effectue la gestion.
Pour résumer:
searchUsers
pourrait aller dans une classe de collection, ou un référentiel / carte d'identité )la source
Écoutez, je pense que votre question est trop générique pour y répondre car nous n'avons pas vraiment de description complète du problème, il est donc impossible de proposer un bon design avec si peu de connaissances. Je peux, à titre d'exemple, répondre à l'une de vos préoccupations concernant l'éventuelle futilité d'une meilleure conception.
Vous dites que votre classe Server et votre future classe Chatroom partagent des données sur les utilisateurs, mais ces données devraient être différentes. Un serveur a probablement un ensemble d'utilisateurs connectés, tandis qu'un Chatroom, qui lui-même appartient à un serveur, a un ensemble d'utilisateurs différent, un sous-ensemble du premier ensemble, composé uniquement des utilisateurs actuellement connectés à un Chatroom spécifique.
Ce ne sont pas les mêmes informations, même si les types de données sont identiques.
Il existe de nombreux avantages à une bonne conception.
Je n'ai pas encore lu le livre de Fowler susmentionné, mais j'ai lu d'autres choses de Folwer et il m'a été recommandé par des personnes en qui j'ai confiance, donc je me sens assez à l'aise pour être d'accord avec les autres.
la source
La nécessité d'accéder aux cartes ne justifie pas la méga-classe. Vous devez séparer la logique en plusieurs classes, et chaque classe doit avoir une méthode getMap pour que les autres classes puissent accéder aux cartes.
la source
J'utiliserais la même réponse que celle que j'ai donnée ailleurs: prendre la classe monolithique et répartir ses responsabilités entre les autres classes. DCI et le modèle de visiteur offrent tous deux de bonnes options pour ce faire.
la source
En termes de métrique logicielle, la grande classe est sac. Il existe des documents illimités prouvant cette déclaration. Pourquoi donc ? car les grandes classes sont plus difficiles à comprendre que les petites classes et il faut plus de temps pour les modifier. De plus, les grandes classes sont si difficiles lorsque vous faites des tests. Et les grandes classes sont très difficiles pour vous lorsque vous souhaitez les réutiliser, car elles contiennent très probablement des éléments indésirables.
la source