Come dividere grandi classi strettamente accoppiate?

13

Ho alcune classi enormi di oltre 2k linee di codice (e in crescita) che vorrei refactoring, se possibile, per avere un po 'più leggero e pulito design.

Il motivo per cui è così grande è principalmente perché queste classi gestiscono un insieme di mappe a cui la maggior parte dei metodi devono accedere, e i metodi sono molto connessi tra loro.

Darò un esempio molto concreto: ho una classe chiamata Server che gestisce i messaggi in arrivo. Ha metodi come joinChatroom , searchUsers , sendPrivateMessage , ecc. Tutti questi metodi manipolano le mappe come users , chatrooms , servers , ...

Forse sarebbe bello se potessi avere una classe che gestisse messaggi riguardanti Chatrooms, un'altra che gestisse tutto su Utenti, ecc. ma il problema principale qui è che ho bisogno di usare tutte le mappe nella maggior parte dei metodi. Ecco perché per ora sono tutti nella classe Server poiché fanno affidamento su queste mappe comuni e i metodi sono molto connessi tra loro.

Avrei bisogno di creare una chat room di classe, ma con un riferimento a ciascuno degli altri oggetti. Un utente di classe di nuovo con un riferimento a tutti gli altri oggetti, ecc.

Mi sento come se stessi facendo qualcosa di sbagliato.

    
posta Matthew 10.08.2012 - 17:56
fonte

9 risposte

10

Dalla tua descrizione, suppongo che le tue mappe siano semplicemente dei sacchi di dati, con tutta la logica nei metodi Server . Spingendo tutte le logiche di chatroom in una classe separata, sei ancora bloccato con mappe contenenti dati.

Invece, prova a modellare singole chat room, utenti, ecc. come oggetti. In questo modo, passerai attorno solo agli oggetti specifici richiesti per un determinato metodo, invece di enormi mappe di dati.

Ad esempio:

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 */
}

Ora è facile chiamare alcuni metodi specifici per gestire i messaggi:

Vuoi entrare in una chat?

chatroom.add(user);

Vuoi inviare un messaggio privato?

user.sendMessage(msg);

Vuoi inviare un messaggio pubblico?

chatroom.sendMessage(msg);
    
risposta data 12.08.2012 - 02:19
fonte
5

Dovresti essere in grado di creare una classe che trattiene ciascuna collezione. Mentre Server avrà bisogno di un riferimento a ciascuna di queste raccolte, è sufficiente la minima quantità di logica che non implicherà l'accesso o la manutenzione delle raccolte sottostanti. Ciò renderà più ovvio esattamente ciò che il server sta facendo e separerà il modo in cui lo fa.

    
risposta data 10.08.2012 - 18:01
fonte
4

Quando ho visto grandi classi come questa ho scoperto che spesso c'è una classe (o più) che cerca di uscire. Se conosci un metodo che ritieni possa non essere correlato a questa classe, rendilo statico. Il compilatore ti dirà poi degli altri metodi che questo methd chiama. Java insisterà che anche loro sono statici. Li fai diventare statici. Di nuovo il compilatore ti dirà di qualsiasi metodo che chiama. Continui a farlo più e più volte finché non hai più errori di compilazione. Quindi hai un carico di metodi statici nella tua grande classe. Ora puoi estrarli in una nuova classe e rendere il metodo non statico. Puoi quindi chiamare questa nuova classe dalla tua grande classe originale (che ora dovrebbe contenere meno righe)

Puoi quindi ripetere il processo finché non sei soddisfatto del design della classe.

Il libro di Martin Fowler è davvero una buona lettura, quindi consiglierei anche questo perché ci sono volte che non puoi usare questo trucco statico.

    
risposta data 10.08.2012 - 18:07
fonte
1

Poiché la maggior parte del tuo codice è già esistente, ti suggerirei di utilizzare le classi di supporto per spostare i tuoi metodi. Ciò aiuterà il facile refactoring. Quindi, la classe del server conterrà comunque le mappe al suo interno. Ma fa uso di una classe helper che dice ChatroomHelper con metodi come join (Map chatrooms, String user), list getUsers (map chatrooms), map getChatrooms (utente String).

La classe server contiene un'istanza di ChatroomHelper, UserHelper ecc, spostando quindi i metodi nelle sue classi helper logiche. Con questo puoi lasciare intatti i metodi pubblici nel server, quindi qualsiasi chiamante non ha bisogno di cambiare.

    
risposta data 10.08.2012 - 18:27
fonte
1

Da aggiungere a risposta perspicace di Casablanca - Se più classi hanno bisogno di fare le stesse cose di base con qualche tipo di entità (aggiungendo utenti a una raccolta, gestendo messaggi, ecc.), quei processi dovrebbero essere tenuti separati.

Ci sono molti modi per farlo: per ereditarietà o composizione. Per l'ereditarietà, puoi utilizzare classi astratte di base con metodi concreti che forniscono campi e funzionalità per gestire utenti o messaggi, ad esempio, e avere entità come chatroom e user (o qualsiasi altra) estendere tali classi.

Per vari motivi , è buona norma utilizzare la composizione sull'ereditarietà. Puoi usare la composizione per farlo in vari modi. Poiché la gestione degli utenti o dei messaggi è una funzione centrale per la responsabilità delle classi, si può sostenere che l'iniezione del costruttore è più appropriata. In questo modo, la dipendenza è trasparente e non è possibile creare un oggetto senza la funzionalità richiesta. Se il modo in cui gli utenti oi messaggi vengono gestiti è probabile che cambi o si estenda, dovresti considerare l'utilizzo di qualcosa come il modello di strategia .

In entrambi i casi, assicurati di codificare verso interfacce, non di classi concrete per flessibilità.

Tutto ciò che viene detto, considera sempre il costo di una maggiore complessità quando si utilizzano tali modelli - se non ne avrai bisogno, non codificarlo. Se sai che probabilmente non cambierai la modalità di gestione degli utenti / messaggi, non avrai bisogno della complessità strutturale di un modello strategico, ma per separare le preoccupazioni ed evitare la ripetizione, dovresti comunque divorziare dalle funzionalità comuni dalle istanze concrete che lo utilizzano e, se non esistono motivi imperativi contrari, comporre gli utenti di tale funzionalità di gestione (chat, utenti) con un oggetto che gestisce la gestione.

Quindi, per riassumere:

  1. Come scrisse casablanca: chat room separate e incapsulate, utenti ecc.
  2. Separa la funzionalità comune
  3. Considerare la separazione delle singole funzionalità per separare la rappresentazione dei dati (nonché l'accesso e la mutazione) da funzionalità più complesse rispetto alle singole istanze di dati o aggregati (ad esempio, qualcosa come searchUsers potrebbe andare in una classe di raccolta o un repository / identity-map)
risposta data 03.02.2015 - 22:54
fonte
0

Guarda, penso che la tua domanda sia troppo generica per rispondere dato che non abbiamo una descrizione completa del problema, quindi offrire un buon design con così poca conoscenza è impossibile. Posso, ad esempio, rispondere a una delle tue preoccupazioni sulla possibile inutilità di un design migliore.

Dici che la tua classe Server e la tua futura classe Chatroom condividono i dati sugli utenti, ma questi dati dovrebbero essere diversi. Probabilmente il server ha un gruppo di utenti connessi ad esso, mentre una Chatroom, che a sua volta appartiene a un server, ha un diverso gruppo di utenti, un sottoinsieme del primo set, di soli utenti attualmente loggati in una Chatroom specifica.

Questa non è la stessa informazione, anche se i tipi di dati sono identici.
Ci sono molti molti vantaggi per un buon design.

Non ho ancora letto il già citato libro di Fowler, ma ho letto altre cose di Folwer e mi è stato consigliato da persone di cui mi fido, quindi mi sento abbastanza a mio agio per concordare con gli altri.

    
risposta data 11.08.2012 - 16:15
fonte
0

La necessità di accedere alle mappe non giustifica la mega-classe. Devi separare la logica in diverse classi e ogni classe deve avere un metodo getMap in modo che altre classi possano accedere alle mappe.

    
risposta data 15.08.2012 - 14:31
fonte
0

Vorrei usare la stessa risposta che ho fornito altrove: prendi la classe monolitica e dividere le sue responsabilità tra le altre classi. Sia DCI che il pattern Visitor forniscono buone opzioni per farlo.

    
risposta data 19.06.2014 - 19:10
fonte
-1

In termini di metrica del software, la grande classe è la borsa. Ci sono documenti illimitati che dimostrano questa affermazione. Perché ? perché le classi più grandi sono più difficili da capire rispetto alle classi piccole ed è necessario più tempo per essere modificate. Inoltre, le classi grandi sono così difficili quando fai test. E le grandi classi sono molto difficili per te quando vuoi riutilizzarle perché è molto probabile che contengano cose indesiderate.

    
risposta data 10.08.2012 - 18:20
fonte

Leggi altre domande sui tag