Risposta in ritardo ma non posso resistere.
X è la maggior parte delle classi in Y buona o un anti-pattern?
Nella maggior parte dei casi, la maggior parte delle regole, applicate senza pensare, andranno per lo più orribilmente sbagliate (compresa questa).
Lascia che ti racconti una storia sulla nascita di un oggetto in mezzo al caos di un codice procedurale in fondo, veloce e sporco, che è accaduto, non per progettazione, ma per disperazione.
Io e il mio stagista stiamo programmando le coppie per creare rapidamente del codice di eliminazione per raschiare una pagina web. Non abbiamo assolutamente motivo di aspettarci che questo codice duri a lungo, quindi stiamo solo sbagliando qualcosa che funziona. Prendiamo l'intera pagina come una stringa e tagliamo le cose di cui abbiamo bisogno nel modo più incredibilmente fragile che tu possa immaginare. Non giudicare. Funziona.
Ora mentre facevo questo ho creato alcuni metodi statici per fare il taglio. Il mio stagista ha creato una classe DTO che era molto simile alla tua CatData
.
Quando ho guardato per la prima volta il DTO mi ha infastidito. Gli anni di danni che Java mi ha fatto nel cervello mi hanno fatto indietreggiare nei campi pubblici. Ma stavamo lavorando in C #. C # non ha bisogno di getter e setter prematuri per preservare il diritto di rendere i dati immutabili o incapsulati in un secondo momento. Senza cambiare l'interfaccia puoi aggiungerle quando vuoi. Forse solo per poter impostare un breakpoint. Tutto senza dire nulla ai tuoi clienti. Sì C #. Boo Java.
Quindi ho tenuto la lingua. Ho visto mentre usava i miei metodi statici per inizializzare questa cosa prima di usarla. Ne avevamo circa 14. Era brutto, ma non avevamo motivo di preoccuparci.
Quindi ne avevamo bisogno in altri posti. Ci siamo ritrovati a voler copiare e incollare il codice. 14 linee di inizializzazione lanciate intorno. Stava iniziando a diventare doloroso. Esitò e mi chiese delle idee.
A malincuore ho chiesto "considereresti un oggetto?"
Guardò di nuovo il suo DTO e si incasinò il viso confuso. "È un oggetto".
"Intendo un oggetto reale"
"Huh?"
"Lascia che ti mostri qualcosa. Decidi se è utile"
Ho scelto un nuovo nome e ho rapidamente creato qualcosa che sembrava un po 'così:
public class Cat{
CatData(string catPage) {
this.catPage = catPage
}
private readonly string catPage;
public string name() { return chop("name prefix", "name suffix"); }
public string weight() { return chop("weight prefix", "weight suffix"); }
public string image() { return chop("image prefix", "image suffix"); }
private string chop(string prefix, string suffix) {
int start = catPage.indexOf(prefix) + prefix.Length;
int end = catPage.indexOf(suffix);
int length = end - start;
return catPage.Substring(start, length);
}
}
Questo non ha fatto nulla che i metodi statici non stessero già facendo. Ma ora avevo risucchiato i 14 metodi statici in una classe in cui potevano essere soli con i dati su cui lavoravano.
Non ho costretto il mio stagista a usarlo. Gliel'ho appena offerto e gli ho lasciato decidere se voleva attenersi ai metodi statici. Tornai a casa pensando che probabilmente si sarebbe attaccato a ciò che già aveva lavorato. Il giorno dopo ho scoperto che lo stava usando in molti posti. Declutrava il resto del codice che era ancora brutto e procedurale, ma questo po 'di complessità era ora nascosto da noi dietro un oggetto. Era un po 'meglio.
Ora certo ogni volta che accedi a questo sta facendo un bel po 'di lavoro. Un DTO è un bel valore memorizzato nella cache veloce. Mi sono preoccupato per questo, ma mi sono reso conto che avrei potuto aggiungere la memorizzazione nella cache se mai ne avessimo avuto bisogno senza toccare il codice di utilizzo. Quindi non mi preoccuperò fino a quando non ci importa.
Sto dicendo che dovresti sempre attenerti agli oggetti OO su quelli DTO? No. Lo splendore di DTO quando devi attraversare un confine che ti impedisce di spostare i metodi. I DTO hanno il loro posto.
Ma anche gli oggetti OO. Scopri come utilizzare entrambi gli strumenti. Scopri quanto costa ciascuno. Impara a decidere il problema, la situazione e il tirocinante. Dogma non è tuo amico qui.
Dal momento che la mia risposta è già ridicolmente lunga, lasciami disabituarti di alcune idee sbagliate con una revisione del tuo codice.
Ad esempio, una classe di solito ha membri e metodi di classe, ad esempio:
public class Cat{
private String name;
private int weight;
private Image image;
public void printInfo(){
System.out.println("Name:"+this.name+",weight:"+this.weight);
}
public void draw(){
//some draw code which uses this.image
}
}
Dov'è il tuo costruttore? Questo non mi sta dimostrando abbastanza per sapere se è utile.
Ma dopo aver letto del principio della responsabilità singola e del principio Open closed, preferisco separare una classe in DTO e una classe di supporto solo con metodi statici, ad esempio:
public class CatData{
public String name;
public int weight;
public Image image;
}
public class CatMethods{
public static void printInfo(Cat cat){
System.out.println("Name:"+cat.name+",weight:"+cat.weight);
}
public static void draw(Cat cat){
//some draw code which uses cat.image
}
}
Penso che si adatti al principio della responsabilità singola perché ora la responsabilità di CatData è di conservare solo i dati, non si preoccupa dei metodi (anche per CatMethods).
Puoi fare molte cose sciocche nel nome del principio di responsabilità singola. Potrei sostenere che Cat Strings e Cat ints dovrebbero essere separati. Che i metodi di disegno e le immagini devono avere tutti una propria classe. Che il tuo programma in esecuzione sia una singola responsabilità, quindi dovresti avere solo una classe. : P
Per me, il modo migliore per seguire il singolo principio di responsabilità è trovare una buona astrazione che ti consenta di mettere la complessità in una scatola in modo da poterla nascondere. Se riesci a dargli un buon nome che impedisce alle persone di essere sorprese da ciò che trovano quando guardano dentro, l'hai seguito abbastanza bene. Aspettarsi che imponga più decisioni di quelle che chiedono problemi. Onestamente, entrambi i tuoi elenchi di codici lo fanno, quindi non vedo perché l'SRP sia importante qui.
E si adatta anche al principio aperto chiuso perché l'aggiunta di nuovi metodi non ha bisogno di cambiare la classe CatData.
Beh no. Il principio open close non riguarda l'aggiunta di nuovi metodi. Si tratta di essere in grado di cambiare l'implementazione di vecchi metodi e di non dover modificare nulla. Niente che ti usi e non i tuoi vecchi metodi. Invece scrivi qualche nuovo codice da qualche altra parte. Qualche forma di polimorfismo lo farà bene. Non lo vedo qui.
La mia domanda è: è un buono o un anti-schema?
Bene diavolo come dovrei saperlo? Guarda, farlo in entrambi i modi ha benefici e costi. Quando si separa il codice dai dati, è possibile modificare entrambi senza dover ricompilare l'altro. Forse questo è di fondamentale importanza per te. Forse rende il tuo codice inutilmente complicato.
Se ti fa sentire meglio non sei così lontano da qualcosa che Martin Fowler chiama un oggetto parametro . Non devi solo prendere le primitive nel tuo oggetto.
Quello che vorrei che tu facessi è sviluppare un senso su come fare la tua separazione, o no, in entrambi gli stili di codifica. Perché ci crediate o no non siete costretti a scegliere uno stile. Devi solo vivere con la tua scelta.