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


15

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

Ho scritto un programma e alla fine ho usato la newparola chiave qua e là, ma erano principalmente per istanziare oggetti che contengono dati (cioè non avevano alcuna funzione o metodo).

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


2
Puoi aggiungere un tag relativo al linguaggio di programmazione? Il nuovo esiste in molte lingue (C ++, Java, Ruby per citarne alcuni) e ha una semantica diversa.
sakisk,

Risposte:


16

Questa è più una guida che una regola rigida.

Usando "nuovo" nel tuo codice di produzione, stai abbinando la tua classe ai suoi collaboratori. Se qualcuno desidera utilizzare un altro collaboratore, ad esempio una sorta di collaboratore fittizio per i test unitari, non può farlo perché il collaboratore viene creato nella logica aziendale.

Naturalmente, qualcuno ha bisogno di creare questi nuovi oggetti, ma questo è spesso meglio lasciare in uno dei due posti: un framework di iniezione di dipendenza come Spring, oppure in qualunque classe stia istanziando la tua classe di logica aziendale, iniettata attraverso il costruttore.

Certo, puoi portarlo troppo lontano. Se vuoi restituire una nuova ArrayList, allora probabilmente stai bene, specialmente se questa sarà una Lista immutabile.

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


1
Bene, se vuoi che sia un elenco immutabile, dovresti usare Collections.unmodifiableListo qualcosa del genere. Ma so cosa intendi :)
MatrixFrog,

Sì, ma in qualche modo devi creare l'elenco originale che poi converti in non modificabile ...
Bill Michell,

5

Il nocciolo 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, non hai fatto nulla di male. Se il tuo utilizzo ha newreso difficile o impossibile testare l'unità del tuo codice, allora dovresti rivalutare il tuo uso di nuovo. Potresti estendere questa analisi all'interazione con altre classi, ma la capacità di scrivere test unitari solidi è spesso un proxy abbastanza buono.


5

In breve, ogni volta che usi "nuovo", stai accoppiando strettamente la classe contenente questo codice all'oggetto che viene creato; per creare un'istanza di uno di questi oggetti, la classe che esegue l'istanza deve conoscere l'istanza della classe concreta. Quindi, quando usi "nuovo", dovresti considerare se la classe in cui stai posizionando l'istanza è un "buon" posto dove risiedere quella conoscenza, e sei disposto a fare cambiamenti in quest'area se la forma del l'oggetto da istanziare doveva cambiare.

L'accoppiamento stretto, ovvero un oggetto con conoscenza di un'altra classe concreta, non è sempre da evitare; a un certo livello, qualcosa, QUALCUNO, deve sapere come creare questo oggetto, anche se tutto il resto si occupa dell'oggetto ricevendone una copia da qualche altra parte. Tuttavia, quando la classe che viene creata 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 in grado di creare questa altra classe diventerà una responsabilità quando si mantiene l'app?" Entrambe le principali metodologie di progettazione (SOLID e GRASP) rispondono di solito "sì", per ragioni leggermente diverse. Tuttavia, sono solo metodologie ed entrambe hanno l'estrema limitazione che non sono state formulate in base alla conoscenza del tuo programma unico. In quanto tali, possono solo errare sul lato della cautela e presumere che qualsiasi punto di accoppiamento stretto possa causare EVENTUALMENTE un problema relativo alla modifica di uno o entrambi i lati di questo punto. Devi prendere una decisione finale conoscendo tre cose; la migliore pratica teorica (che consiste nell'associare liberamente tutto perché tutto può cambiare); il costo dell'attuazione della migliore pratica teorica (che può comprendere diversi nuovi livelli di astrazione che faciliterà un tipo di cambiamento mentre ne ostacolerà un altro); e la probabilità nel mondo reale che il tipo di cambiamento che stai anticipando sarà mai necessario.

Alcune linee guida generali:

  • Evitare l'accoppiamento stretto tra le librerie di codici 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 modifica una classe A nella DLL X e la classe B nella 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ù consentito poiché l'intero binario deve essere ricostruito per qualsiasi modifica comunque. A volte, è necessario evitare la ricostruzione di più file binari, ma è necessario strutturare il codice in modo da poterlo evitare, ove possibile, soprattutto per le situazioni in cui la larghezza di banda è limitata (come la distribuzione di app mobili; il push di una nuova DLL in un aggiornamento è molto più economico che 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 livelli di applicazioni tradizionali, come UI, Controller, Dominio, DAO, Dati; sezioni verticali potrebbero essere definite per singole finestre o viste o per singole "storie utente" (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, a sinistra o a destra in un sistema ben strutturato, si dovrebbe generalmente 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 la logica avanzata che coinvolge un'altra finestra, dovrebbe astrarre l'attivazione di questa logica tramite un evento e / o callback; non deve sapere cosa verrà fatto di conseguenza, consentendo di modificare ciò che verrà fatto senza cambiare il controllo che lo attiva.

  • In ogni caso, considera quanto sarà facile o difficile apportare una modifica e con quale probabilità tale modifica sarà. Se un oggetto che si sta creando viene sempre usato da un solo punto e non si prevede che il cambio, allora l'accoppiamento stretto è generalmente più ammissibile e può anche essere superiore in questa situazione all'accoppiamento lento. L'accoppiamento lento richiede l'astrazione, che è un livello aggiuntivo che impedisce la modifica di 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 aumenta effettivamente la quantità di lavoro necessaria per apportare la modifica. Devi soppesare la probabilità di diversi tipi di modifiche che incidono sulla progettazione,


3

Gli oggetti che contengono solo dati o DTO (oggetti di trasferimento dati) vanno bene, creano quelli tutto il giorno. Dove si desidera evitare newè sulle classi che eseguono azioni, si desidera che le classi che consumano programmino invece sull'interfaccia , dove possono invocare quella logica e consumarla, ma non essere responsabili della creazione effettiva delle istanze delle classi che contengono la logica . Quelle classi performanti o contenenti la logica sono dipendenze. Il discorso di Misko è orientato verso di te iniettando quelle dipendenze e lasciando che qualche altra entità sia responsabile della loro effettiva creazione.


2

Altri hanno già menzionato questo punto, ma hanno voluto citare questo dal codice pulito dello zio Bob (Bob Martin) perché potrebbe rendere più semplice la comprensione del concetto:

"Un potente meccanismo per separare la costruzione dall'uso è la Dependency Injection (DI), l'applicazione di Inversion of Control (IoC) alla gestione delle dipendenze. Inversion of Control sposta le responsabilità secondarie da un oggetto ad altri oggetti dedicati allo scopo, supportando così il principio della responsabilità singola . Nel contesto della gestione delle dipendenze, un oggetto non dovrebbe assumersi la responsabilità di istanziare le dipendenze stesse, ma dovrebbe passare questa responsabilità a un altro meccanismo "autorevole", invertendo così il controllo. Poiché l'installazione è una preoccupazione globale, questo il 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 -> separa la tua classe dalle sue dipendenze (collaboratori). La seconda ragione è che rende le tue classi più piccole e più difficili, più facili da capire e persino da riutilizzare in altri contesti.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.