Quando dovresti e dovresti non usare la "nuova" parola chiave?

14

Ho visto una presentazione di Google Tech Talk su Unit Testing , offerta da Misko Hevery, e ha detto di evitare utilizzando la parola chiave new nel codice della logica aziendale.

Ho scritto un programma, e ho finito per usare la parola chiave new qua e là, ma erano principalmente per istanziare oggetti che contengono dati (cioè, non avevano funzioni o metodi).

Mi chiedo, ho fatto qualcosa di sbagliato quando ho usato la nuova parola chiave per il mio programma. E dove possiamo infrangere quella "regola"?

    
posta skizeey 24.10.2011 - 20:02
fonte

5 risposte

16

Questa è una guida più che una regola rigida.

Usando "nuovo" nel tuo codice di produzione, stai accoppiando la tua classe con i suoi collaboratori. Se qualcuno vuole utilizzare qualche altro collaboratore, ad esempio una specie di collaboratore fittizio per il test delle unità, non può farlo perché il collaboratore è stato creato nella logica aziendale.

Certo, qualcuno ha bisogno di creare questi nuovi oggetti, ma questo è meglio lasciarlo in uno dei due seguenti posti: un framework per le dipendenze come Spring, oppure in qualsiasi classe si stia creando un'istanza della classe logica aziendale, iniettata attraverso il costruttore.

Certo, puoi prenderlo troppo lontano. Se vuoi restituire un nuovo ArrayList, probabilmente stai bene - specialmente se questo sarà un elenco immutabile.

La domanda principale che dovresti porci è "è la responsabilità principale di questo bit di codice per creare oggetti di questo tipo, o è solo un dettaglio di implementazione che posso ragionevolmente spostare altrove?"

    
risposta data 24.10.2011 - 20:10
fonte
5

Il punto cruciale di questa domanda è alla fine: Mi chiedo, ho fatto qualcosa di sbagliato quando ho usato la nuova parola chiave per il mio programma. E dove possiamo infrangere quella "regola"?

Se sei in grado di scrivere test unitari efficaci per il tuo codice, allora non hai fatto nulla di sbagliato. Se il tuo utilizzo di new ha reso difficile o impossibile testare il tuo codice unitario, dovresti rivalutare l'utilizzo di nuovi. È possibile estendere questa analisi all'interazione con altre classi, ma la possibilità di scrivere test di unità solidi è spesso un proxy abbastanza buono.

    
risposta data 24.10.2011 - 20:25
fonte
5

In breve, ogni volta che usi "nuovo", stai strettamente accoppiando la classe che contiene questo codice all'oggetto che si sta creando; per istanziare uno di questi oggetti, la classe che fa l'istanziazione deve sapere della istanza della classe concreta. Quindi, quando usi "nuovo", dovresti considerare se la classe in cui stai ponendo l'istanziazione è un posto "buono" in cui risiede la conoscenza, e sei disposto a fare cambiamenti in quest'area se la forma del l'oggetto istanziato doveva cambiare.

L'accoppiamento stretto, cioè un oggetto che ha conoscenza di un'altra classe concreta, non è sempre da evitare; a un certo livello, qualcosa, ALCUNE PARTI, deve sapere come creare questo oggetto, anche se tutto il resto riguarda l'oggetto perché ne viene fornita una copia da qualche altra parte. Tuttavia, quando la classe in fase di creazione cambia, qualsiasi classe che conosce l'implementazione concreta di quella classe deve essere aggiornata per gestire correttamente le modifiche di quella classe.

La domanda che dovresti sempre porre è: "Avere questa classe sapere come creare quest'altra classe diventerà una responsabilità quando manterrai l'app?" Entrambe le principali metodologie di progettazione (SOLID e GRASP) di solito rispondono "sì", per ragioni leggermente diverse. Tuttavia, sono solo metodologie ed entrambi hanno il limite estremo che non sono stati formulati in base alla conoscenza del tuo programma unico. In quanto tali, possono solo commettere errori di prudenza e presumere che qualsiasi punto di accoppiamento stretto causerà EVENTUALMENTE un problema relativo all'effetto di apportare modifiche a uno o entrambi i lati di questo punto. Devi prendere una decisione definitiva conoscendo tre cose; la migliore pratica teorica (che è quella di accoppiare liberamente tutto perché tutto può cambiare); il costo di implementazione della best practice teorica (che può includere diversi nuovi livelli di astrazione che faciliteranno un tipo di cambiamento mentre ne ostacoleranno un altro); e la probabilità del mondo reale che il tipo di cambiamento che stai anticipando sarà sempre necessario.

Alcune linee guida generali:

  • Evita l'accoppiamento stretto tra le librerie di codice compilate. L'interfaccia tra DLL (o un EXE e le sue DLL) è il luogo principale in cui l'accoppiamento stretto presenterà uno svantaggio. Se si apporta una modifica a una classe A nella DLL X e la classe B nel file EXE principale conosce la classe A, è necessario ricompilare e rilasciare entrambi i file binari. All'interno di un singolo binario, l'accoppiamento più stretto è generalmente più permissibile perché l'intero binario deve essere ricostruito per qualsiasi cambiamento comunque. A volte, dover ricostruire più file binari è inevitabile, ma è necessario strutturare il codice in modo da poterlo evitare laddove possibile, soprattutto per le situazioni in cui la larghezza di banda è scarsa (ad esempio la distribuzione di app mobili, la sostituzione di una nuova DLL in un aggiornamento è molto più economica di spingere l'intero programma).

  • Evita l'accoppiamento stretto tra i principali "centri logici" del tuo programma. Puoi pensare a un programma ben strutturato come composto da sezioni orizzontali e verticali. Le sezioni orizzontali possono essere i livelli di applicazione tradizionali, come interfaccia utente, controller, dominio, DAO, dati; le sezioni verticali potrebbero essere definite per singole finestre o viste o per singole "user story" (come la creazione di un nuovo record di un tipo di base). Quando si effettua una chiamata che si sposta verso l'alto, il basso, sinistra o destra in un sistema ben strutturato, si dovrebbe in genere astrarre detta chiamata. Ad esempio, quando la convalida deve recuperare i dati, non dovrebbe avere accesso diretto al DB, ma dovrebbe effettuare una chiamata a un'interfaccia per il recupero dei dati, che è supportata dall'oggetto reale che sa come farlo. Quando alcuni controlli dell'interfaccia utente devono eseguire una logica avanzata che coinvolga un'altra finestra, dovrebbero astrarre l'attivazione di questa logica tramite un evento e / o un callback; non deve sapere cosa verrà fatto di conseguenza, permettendoti di cambiare ciò che verrà fatto senza cambiare il controllo che lo innesca.

  • In ogni caso, considera quanto un cambiamento sarà facile o difficile e quanto probabile sarà il cambiamento. Se un oggetto che stai creando viene sempre e solo usato da un posto, e non prevedi che cambi, l'accoppiamento stretto è generalmente più ammissibile, e potrebbe anche essere superiore in questa situazione per perdere l'accoppiamento. L'accoppiamento lento richiede l'astrazione, che è un livello aggiuntivo che impedisce il passaggio agli oggetti dipendenti quando l'implementazione di una dipendenza deve cambiare. Tuttavia, se l'interfaccia stessa deve cambiare (aggiungendo una nuova chiamata di metodo o aggiungendo un parametro a una chiamata di metodo esistente), un'interfaccia effettivamente aumenta la quantità di lavoro necessaria per apportare la modifica. Devi valutare la probabilità che diversi tipi di cambiamenti abbiano un impatto sul design e che sia impossibile o impossibile realizzare un sistema che è chiuso a tutti i tipi di cambiamento.

risposta data 25.10.2011 - 00:26
fonte
3

Gli oggetti che contengono solo dati o DTO (oggetti di trasferimento dati) vanno bene, crea quelli per tutto il giorno. Dove vuoi evitare new è su classi che eseguono azioni, vuoi che le classi che consumano invece programmino contro l'interfaccia , dove possono invocare quella logica e consumarla, ma non essere responsabile per effettivamente creando le istanze delle classi che contengono la logica. Quelle classi che contengono azioni o logiche sono dipendenze. Il discorso di Misko è mirato a iniettare tali dipendenze e lasciare che qualche altra entità sia responsabile della loro effettiva creazione.

    
risposta data 24.10.2011 - 20:08
fonte
2

Altri hanno già menzionato questo punto, ma volevano citarlo dal codice pulito di Uncle Bob (Bob Martin) perché potrebbe rendere più semplice la comprensione del concetto:

"Un potente meccanismo per separare la costruzione dall'uso è Iniezione di dipendenza (DI), l'applicazione di Inversion of Control (IoC) alla gestione delle dipendenze. responsabilità secondarie da un oggetto ad altri oggetti che sono dedicati allo scopo, supportando in tal modo il Principio di Responsabilità Singola . Nel contesto della gestione delle dipendenze, un oggetto non dovrebbe assumersi la responsabilità di creare istanze di dipendenza. dovrebbe passare questa responsabilità a un altro meccanismo "autorevole", invertendo in tal modo il controllo: poiché l'installazione è una preoccupazione globale, questo meccanismo autorevole di solito sarà la "principale" routine o un contenitore per scopi speciali. "

Penso che sia sempre una buona idea seguire questa regola. Altri hanno menzionato il più importante IMO - > disaccoppia la tua classe dalle sue dipendenze (collaboratori). La seconda ragione è che rende le tue classi più piccole e più terse, più facili da capire e persino riutilizzate in altri contesti.

    
risposta data 24.10.2011 - 20:39
fonte

Leggi altre domande sui tag