web-dev-qa-db-fra.com

Comment diviser de grandes classes étroitement couplées?

J'ai d'énormes classes de plus de 2 000 lignes de code (et de croissance) que je souhaiterais refroidir si possible, d'avoir un design plus léger et propre.

La raison pour laquelle il est si grand est essentiellement parce que ces classes gèrent un ensemble de cartes que la plupart des méthodes doivent accéder, et des 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 telles que joinChatroom, searchUsers, sendPrivateMessage, etc. Toutes ces méthodes manipulent les cartes telles que users, chatrooms, servers, ...

Peut-être que ce serait bien si je pouvais avoir des messages de manutention de classe concernant des salles de discussion, une autre manipulation sur les utilisateurs, etc. Mais le problème principal ici est que je dois utiliser toutes les cartes de la plupart des méthodes. C'est pourquoi pour l'instant, ils sont tous collé dans la classe Server car ils s'appuient tous sur ces cartes communes et les méthodes sont très connectées les unes aux autres.

Je devrais créer une salle de discussion de classe, mais avec une référence à chacun des autres objets. Un utilisateur de classe à nouveau avec une référence à tous les autres objets, etc.

J'ai l'impression de faire quelque chose de mal.

14
Matthew

De votre description, je suppose que vos cartes sont purement des sacs de données, avec toute la logique des méthodes Server. En poussant toute la logique de la salle de discussion dans une classe distincte, vous êtes toujours bloqué avec des cartes contenant des données.

Au lieu de cela, essayez de modéliser des salles de discussion individuelles, des utilisateurs, etc. comme 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:

public class User {
  private String name;
  ...

  public void sendMessage(String message) {
    ...
  }
}

public class Chatroom {
  // users in this chatroom
  private Collection<User> users;

  public void add(User user) {
    users.add(user);
  }

  public void sendMessage(String msg) {
    for (User user : users)
      user.sendMessage(msg);
  }
}

public class Server {
  // all users on the server
  private Collection<User> users;

  // all chatrooms on the server
  private Collection<Chatroom> chatrooms;

  /* methods to handle incoming messages */
}

Maintenant, il est facile d'appeler quelques méthodes spécifiques pour gérer les messages:

Vous voulez rejoindre une salle de discussion?

chatroom.add(user);

Voulez-vous envoyer un message privé?

user.sendMessage(msg);

Voulez-vous envoyer un message public?

chatroom.sendMessage(msg);
10
casablanca

Vous devriez être capable de créer une classe qui contient chaque collection. Tandis que 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'implique pas d'accéder à l'accès ou de maintenir les collections sous-jacentes. Cela rendra plus évident exactement ce que le serveur fait et séparez comment cela le fait.

5
Peter Lawrey

Quand j'ai vu de grandes classes comme celle-ci, j'ai constaté qu'il y avait souvent une classe (ou plus) là-bas essayant de sortir. Si vous connaissez une méthode que vous pensez ne pas être liée à cette classe, alors faites-la statique. Le compilateur vous indiquera ensuite d'autres méthodes que ce METHD appelle. =Java== insistera, ils sont statiques aussi. Vous les faites statique. Encore une fois, le compilateur vous dira de toute méthode qu'il appelle. Vous continuez à faire cela encore et encore jusqu'à ce que vous n'ayez plus de défaillances de compilation. . Ensuite, vous avez une charge de méthodes statiques dans votre grande classe. Vous pouvez maintenant les extraire dans une nouvelle classe et faire 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 recommanderais cela aussi comme il y a des moments où vous ne pouvez pas utiliser ce truc statique.

4
RNJ

Ajouter à la réponse perspicace de Casablanca - Si plusieurs classes doivent faire la même chose de base avec un type d'entité (ajoutant des utilisateurs à une collection, des messages de manipulation, etc.), ces processus doivent être conservés séparés également.

Il existe de multiples façons de faire cela - par héritage ou composition. Pour héritage, vous pouvez utiliser des classes de base abstraites avec des méthodes de béton fournissant les champs et la fonctionnalité pour gérer les utilisateurs ou les messages par exemple, et avoir des entités comme chatroom et user (ou tout autre) étendre ces Des classes.

Pour diverses raisons , c'est une bonne règle générale d'utiliser la composition sur 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 sont des fonctions centrales à la responsabilité des classes, on peut soutenir que l'injection du 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 manipulés est susceptible de changer ou d'être étendu, vous devez envisager d'utiliser quelque chose comme le Strategy Standing .

Dans les deux cas, veillez à coder vers des interfaces, pas de classes concrètes pour la flexibilité.

Tout ce qui étant dit, considérez toujours le coût de la complexité supplémentaire lors de l'utilisation de tels modèles - si vous n'allez pas en avoir besoin, ne le codez pas. Si vous savez que vous n'allez probablement pas changer comment 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 afin de séparer les préoccupations et d'éviter la répétition, vous devez toujours divorcer la fonctionnalité commune Des instances concrètes qui l'utilisent - et, si aucune raison primordiale du contraire n'existent, composez les utilisateurs de cette fonctionnalité de manutention (salles de discussion, utilisateurs) avec un objet qui fait la manipulation.

Pour résumer:

  1. Comme Casablanca a écrit: séparez et encapsulez des salles de discussion, des utilisateurs, etc.
  2. Fonctionnalité commune distincte
  3. Envisager de séparer les fonctionnalités individuelles pour divorcer la représentation de données (ainsi que l'accès et la mutation) de fonctionnalités plus complexes sur des instances individuelles de données ou d'agrégats de ceux-ci (par exemple, quelque chose comme searchUsers pourrait aller dans une classe de collecte, ou Un référentiel/carte d'identité)
1
Michael Bauer

Étant donné que la majeure partie de votre code existe, je suggérerais d'utiliser des classes d'assistance pour sortir vos méthodes. Cela aidera facilement refactoring. Donc, votre classe de serveur contiendra toujours les cartes dedans. Mais il utilise une classe d'assistance dise Chatroomhelper avec des méthodes telles que rejoindre (cartographier des salles de discussion, String User), Liste GetUserers (salles de discussion de la carte), la carte getchatrooms (String User).

La classe de serveur contiendra une instance de ChatroomHelper, UserHelper, etc. déplaçant ainsi les méthodes vers ses classes logiques. WTIH Ceci vous pouvez laisser les méthodes publiques dans le serveur intact, de sorte que tout appelant n'a pas besoin de changer.

1
techuser soma

J'utiliserais la même réponse J'ai fourni ailleurs: prenez la classe monolithique et divisez ses responsabilités entre d'autres classes. DCI et le modèle de visiteur offrent à la fois de bonnes options pour le faire.

0
Mario T. Lanza

Regardez, je pense que votre question est trop générique pour répondre car nous n'avons pas vraiment de description complète du problème, offrant ainsi un bon design avec si peu de connaissances est impossible. Je peux, tout comme un exemple, adresser l'une de vos préoccupations concernant la futilité possible d'une meilleure conception.

Vous dites que votre classe de serveur et votre future classe de discussion partagent des données sur les utilisateurs, mais ces données doivent être différentes. Le serveur dispose probablement d'un ensemble d'utilisateurs qui lui sont associés, tandis qu'une salle de discussion, qui appartient elle-même à un serveur, a un ensemble d'utilisateurs différents, un sous-ensemble de la première série, uniquement des utilisateurs connectés à une salle de discussion spécifique.

Ce n'est pas la même information, même si les types de données sont identiques.
[.____] Il y a beaucoup d'avantages à un bon design.

Je n'ai pas lu le livre susmentionné par Fowler, mais j'ai lu d'autres choses par fallwer et il m'a été recommandé par les gens que j'ai confiance, alors je me sens assez à l'aise pour être d'accord avec les autres.

0
Shrulik

La nécessité d'accéder aux cartes ne justifie pas le méga-classe. Vous devez séparer la logique dans plusieurs classes et chaque classe doit avoir une méthode getMap afin d'accéder aux cartes.

0
Tulains Córdova