Solo perché un sistema è complesso non significa che devi renderlo complicato . Se hai una classe che ha troppe dipendenze (o collaboratori) come questa:
public class MyAwesomeClass {
public class MyAwesomeClass(IDependency1 _d1, IDependency2 _d2, ... , IDependency20 _d20) {
// Assign it all
}
}
... allora è diventato troppo complicato e non stai davvero seguendo SRP , vero? Scommetto che se annotassi ciò che MyAwesomeClass
fa su una scheda CRC non si adatterebbe su una scheda indice o dovresti scrivere in lettere illeggibili davvero minuscole.
Quello che hai qui è che i tuoi ragazzi hanno seguito solo il Principio di segregazione dell'interfaccia invece e potrebbero averlo portato all'estremo ma questa è tutta un'altra storia. Si potrebbe obiettare che le dipendenze sono oggetti di dominio (cosa che succede), tuttavia avere una classe che gestisce 20 oggetti di dominio contemporaneamente lo sta allungando un po 'troppo.
TDD ti fornirà un buon indicatore di quanto fa una classe. Senza mezzi termini; se un metodo di test ha un codice di installazione che richiede un'eternità per scrivere (anche se si esegue il refactoring dei test), allora MyAwesomeClass
probabilmente ci sono troppe cose da fare.
Quindi, come risolvi questo enigma? Sposta le responsabilità in altre classi. Esistono alcuni passaggi che è possibile eseguire su una classe che presenta questo problema:
- Identifica tutte le azioni (o responsabilità) che la tua classe fa con le sue dipendenze.
- Raggruppare le azioni in base a dipendenze strettamente correlate.
- Redelegate! Vale a dire refactoring ciascuna delle azioni identificate a nuove o (soprattutto) altre classi.
Un esempio astratto di responsabilità di refactoring
Lasciate C
essere una classe che ha diverse dipendenze D1
, D2
, D3
, D4
che è necessario refactoring di usare meno. Quando identifichiamo quali metodi che fanno C
appello alle dipendenze possiamo fare un semplice elenco:
D1
- performA(D2)
,performB()
D2
- performD(D1)
D3
- performE()
D4
- performF(D3)
Guardando l'elenco possiamo vederlo D1
e D2
siamo in relazione tra loro in quanto la classe ne ha bisogno insieme in qualche modo. Possiamo anche vedere che ha D4
bisogno D3
. Quindi abbiamo due raggruppamenti:
Group 1
- D1
<->D2
Group 2
- D4
->D3
I raggruppamenti indicano che la classe ha ora due responsabilità.
Group 1
- Uno per gestire i due oggetti chiamanti che hanno bisogno l'uno dell'altro. Forse puoi permettere alla tua classe di C
eliminare la necessità di gestire entrambe le dipendenze e lasciare invece una di esse che gestisce quelle chiamate. In questo gruppo, è ovvio che D1
potrebbe avere un riferimento D2
.
Group 2
- L'altra responsabilità ha bisogno di un oggetto per chiamarne un altro. Non riesci a D4
gestire al D3
posto della tua classe? Quindi probabilmente possiamo eliminare D3
dalla classe C
lasciando invece D4
effettuare le chiamate.
Non prendere la mia risposta incastonata nella pietra, poiché l'esempio è molto astratto e fa molte ipotesi. Sono abbastanza sicuro che ci sono altri modi per riformattare questo, ma almeno i passaggi potrebbero aiutarti a ottenere un qualche tipo di processo per spostare le responsabilità invece di dividere le classi.
Modificare:
Tra i commenti di @Emmad Karem afferma:
"Se la tua classe ha 20 parametri nel costruttore, non sembra che il tuo team sappia esattamente cosa sia SRP. Se hai una classe che fa solo una cosa, come può avere 20 dipendenze?" - Penso che se tu avere una classe Customer, non è strano avere 20 parametri nel costruttore.
È vero che gli oggetti DAO tendono ad avere molti parametri, che devi impostare nel tuo costruttore, e i parametri sono di solito tipi semplici come stringa. Tuttavia, nell'esempio di una Customer
classe, puoi ancora raggruppare le sue proprietà all'interno di altre classi per semplificare le cose. Ad esempio avere una Address
classe con strade e una Zipcode
classe che contiene il codice postale e gestirà anche la logica aziendale come la convalida dei dati:
public class Address {
private String street1;
//...
private Zipcode zipcode;
// easy to extend
public bool isValid() {
return zipcode.isValid();
}
}
public class Zipcode {
private string zipcode;
public bool isValid() {
// return regex match that zipcode contains numbers
}
}
Questa cosa è discussa ulteriormente nel post del blog "Mai, mai, mai usare String in Java (o almeno spesso)" . In alternativa all'utilizzo di costruttori o metodi statici per semplificare la creazione di oggetti secondari, è possibile utilizzare un modello di generatore di fluido .