Progettare una classe per prendere intere classi come parametri piuttosto che singole proprietà


30

Diciamo, ad esempio, che hai un'applicazione con una classe ampiamente condivisa chiamata User. Questa classe espone tutte le informazioni sull'utente, il loro ID, nome, livelli di accesso a ciascun modulo, fuso orario ecc.

I dati dell'utente sono ovviamente ampiamente referenziati in tutto il sistema, ma per qualsiasi motivo, il sistema è impostato in modo tale che invece di passare questo oggetto utente in classi che dipendono da esso, stiamo semplicemente passando singole proprietà da esso.

Una classe che richiede l'id utente, richiederà semplicemente il GUID userIdcome parametro, a volte potrebbe essere necessario anche il nome utente, quindi viene passato come parametro separato. In alcuni casi, questo viene passato ai singoli metodi, quindi i valori non vengono affatto mantenuti a livello di classe.

Ogni volta che ho bisogno di accedere a informazioni diverse dalla classe User, devo apportare modifiche aggiungendo parametri e laddove l'aggiunta di un nuovo sovraccarico non è appropriata, devo cambiare anche ogni riferimento al metodo o al costruttore della classe.

L'utente è solo un esempio. Questo è ampiamente praticato nel nostro codice.

Ho ragione nel pensare che si tratti di una violazione del principio Open / Closed? Non solo l'atto di cambiare le classi esistenti, ma di istituirle in primo luogo in modo che i cambiamenti diffusi saranno molto probabilmente richiesti in futuro?

Se abbiamo appena passato l' Useroggetto, potrei apportare una piccola modifica alla classe con cui sto lavorando. Se devo aggiungere un parametro, potrei dover fare dozzine di modifiche ai riferimenti alla classe.

Ci sono altri principi infranti da questa pratica? Inversione di dipendenza forse? Anche se non stiamo facendo riferimento a un'astrazione, esiste solo un tipo di utente, quindi non c'è alcuna reale necessità di avere un'interfaccia utente.

Esistono altri principi non SOLID violati, come i principi di base della programmazione difensiva?

Il mio costruttore dovrebbe apparire così:

MyConstructor(GUID userid, String username)

O questo:

MyConstructor(User theUser)

Modifica post:

È stato suggerito di rispondere alla domanda in "Pass ID o oggetto?". Questo non risponde alla domanda su come la decisione di andare in entrambi i modi influisca sul tentativo di seguire i principi SOLIDI, che è al centro di questa domanda.


11
@gnat: questo non è sicuramente un duplicato. Il possibile duplicato riguarda il concatenamento di metodi per raggiungere in profondità una gerarchia di oggetti. Questa domanda non sembra chiedersi affatto.
Greg Burghardt,

2
Il secondo modulo viene spesso utilizzato quando il numero di parametri passati è diventato ingombrante.
Robert Harvey,

12
L'unica cosa che non mi piace della prima firma è che non esiste alcuna garanzia che userId e nome utente provengano effettivamente dallo stesso utente. È un potenziale bug che viene evitato passando in giro l'utente ovunque. Ma la decisione dipende davvero da cosa stanno facendo i metodi chiamati con gli argomenti.
17 del 26

9
La parola "analisi" non ha senso nel contesto in cui la stai usando. Intendi invece "passare"?
Konrad Rudolph,

5
Che dire di Iin SOLID? MyConstructorsostanzialmente dice ora "Ho bisogno di un Guide un string". Quindi perché non avere un'interfaccia che fornisce a Guide a string, lasciare Userimplementare quell'interfaccia e lasciare MyConstructordipendere da un'istanza che implementa quell'interfaccia? E se le esigenze di MyConstructorcambiamento, cambiano l'interfaccia. - Mi ha aiutato molto a pensare alle interfacce per "appartenere" al consumatore piuttosto che al fornitore . Quindi pensa "come consumatore ho bisogno di qualcosa che faccia questo e quello" invece di "come fornitore posso fare questo e quello".
Corak,

Risposte:


31

Non c'è assolutamente nulla di sbagliato nel passare un intero Useroggetto come parametro. In effetti, potrebbe aiutare a chiarire il codice e rendere più ovvio per i programmatori cosa richiede un metodo se la firma del metodo richiede un User.

Passare semplici tipi di dati è bello, fino a quando significano qualcosa di diverso da quello che sono. Considera questo esempio:

public class Foo
{
    public void Bar(int userId)
    {
        // ...
    }
}

E un esempio di utilizzo:

var user = blogPostRepository.Find(32);
var foo = new Foo();

foo.Bar(user.Id);

Riesci a individuare il difetto? Il compilatore non può. L '"ID utente" che viene passato è solo un numero intero. Chiamiamo la variabile userma inizializziamo il suo valore blogPostRepositorydall'oggetto, che presumibilmente restituisce BlogPostoggetti, non Useroggetti - tuttavia il codice si compila e si finisce con un errore di runtime traballante.

Ora considera questo esempio alterato:

public class Foo
{
    public void Bar(User user)
    {
        // ...
    }
}

Forse il Barmetodo utilizza solo l '"ID utente" ma la firma del metodo richiede un Useroggetto. Ora torniamo allo stesso esempio di utilizzo di prima, ma modificalo per passare l'intero "utente" in:

var user = blogPostRepository.Find(32);
var foo = new Foo();

foo.Bar(user);

Ora abbiamo un errore del compilatore. Il blogPostRepository.Findmetodo restituisce un BlogPostoggetto, che abilmente chiamiamo "utente". Quindi passiamo questo "utente" al Barmetodo e otteniamo prontamente un errore del compilatore, perché non possiamo passare BlogPosta un metodo che accetta un User.

Il sistema di tipo della lingua viene sfruttato per scrivere il codice corretto più rapidamente e identificare i difetti in fase di compilazione, anziché in fase di esecuzione.

Davvero, dover refactificare un sacco di codice perché i cambiamenti delle informazioni dell'utente sono semplicemente un sintomo di altri problemi. Passando un intero Useroggetto si ottengono i vantaggi di cui sopra, oltre ai vantaggi di non dover riformattare tutte le firme dei metodi che accettano le informazioni dell'utente quando Usercambia qualcosa sulla classe.


6
Direi che il tuo ragionamento da solo in realtà punta verso il passaggio di campi, ma avere i campi essere banali attorno al valore reale. In questo esempio, un Utente ha un campo di tipo UserID e un UserID ha un singolo campo con valori interi. Ora la dichiarazione di Bar ti dice immediatamente che Bar non usa tutte le informazioni sull'Utente, solo il loro ID, ma non puoi ancora fare errori stupidi come passare un numero intero che non proviene da un UserID in Bar.
Ian,

(Cont.) Naturalmente questo tipo di stile di programmazione è piuttosto noioso, specialmente in un linguaggio che non ha un buon supporto sintattico per esso (Haskell è carino per questo stile, ad esempio, dato che puoi semplicemente abbinare su "ID ID utente") .
Ian,

5
@Ian: Penso che racchiudere un ID nei suoi pattini di tipo attorno al problema originale sollevato dall'OP, che è che le modifiche strutturali alla classe User rendono necessario il refactoring di molte firme di metodo. Il passaggio dell'intero oggetto utente risolve questo problema.
Greg Burghardt,

@Ian: Anche se a dire il vero, anche lavorando in C # sono stato molto tentato di racchiudere gli ID e il tipo in uno Struct solo per dare un po 'più di chiarezza.
Greg Burghardt,

1
"non c'è niente di sbagliato nel passare un puntatore al suo posto." O un riferimento, per evitare tutti i problemi con i puntatori che potresti incontrare.
Yay295,

17

Ho ragione nel pensare che si tratti di una violazione del principio Open / Closed?

No, non è una violazione di tale principio. Tale principio riguarda il fatto di non modificare le Usermodalità che incidono su altre parti del codice che lo utilizzano. Le tue modifiche a Userpotrebbero essere una tale violazione, ma non sono correlate.

Ci sono altri principi infranti da questa pratica? Forse l'inversione di dipendenza?

No. Quello che descrivi - iniettando solo le parti richieste di un oggetto utente in ciascun metodo - è l'opposto: è pura inversione di dipendenza.

Esistono altri principi non SOLID violati, come i principi di base della programmazione difensiva?

No. Questo approccio è un modo di codifica perfettamente valido. Non sta violando tali principi.

Ma l'inversione di dipendenza è solo un principio; non è una legge infrangibile. E DI puro può aggiungere complessità al sistema. Se scopri che solo iniettando i valori utente necessari nei metodi, anziché passare l'intero oggetto utente nel metodo o nel costruttore, crea problemi, quindi non farlo in questo modo. Si tratta di trovare un equilibrio tra principi e pragmatismo.

Per rispondere al tuo commento:

Ci sono problemi con la necessità di analizzare inutilmente un nuovo valore in cinque livelli della catena e quindi modificare tutti i riferimenti a tutti e cinque i metodi esistenti ...

Parte del problema qui è che chiaramente non ti piace questo approccio, secondo il commento "inutilmente [pass] ...". E questo è abbastanza giusto; non c'è una risposta giusta qui. Se lo trovi gravoso, non farlo in questo modo.

Tuttavia, per quanto riguarda il principio aperto / chiuso, se lo segui rigorosamente allora "... cambia tutti i riferimenti a tutti e cinque i metodi esistenti ..." è un'indicazione che tali metodi sono stati modificati, quando dovrebbero essere chiuso a modifica. In realtà, tuttavia, il principio di apertura / chiusura ha un buon senso per le API pubbliche, ma non ha molto senso per gli interni di un'app.

... ma sicuramente un piano per rispettare questo principio, nella misura del possibile, includerebbe strategie per ridurre la necessità di futuri cambiamenti?

Ma poi vaghi nel territorio di YAGNI e sarebbe ancora ortogonale al principio. Se hai un metodo Fooche prende un nome utente e quindi vuoi Fooprendere anche una data di nascita, seguendo il principio, aggiungi un nuovo metodo; FooRimane invariato. Ancora una volta è una buona pratica per le API pubbliche, ma è una sciocchezza per il codice interno.

Come accennato in precedenza, si tratta di equilibrio e buon senso per ogni situazione. Se questi parametri cambiano spesso, quindi sì, utilizzare Userdirettamente. Ti salverà dalle modifiche su larga scala che descrivi. Ma se non cambiano spesso, passare anche solo ciò che è necessario è un buon approccio.


Ci sono problemi con la necessità di analizzare inutilmente un nuovo valore in cinque livelli della catena e quindi modificare tutti i riferimenti a tutti e cinque i metodi esistenti. Perché il principio Open / Closed si applica solo alla classe User e non anche alla classe che sto modificando, che viene utilizzata anche da altre classi? Sono consapevole che il principio riguarda specificamente l'evitamento del cambiamento, ma sicuramente un piano per attenersi a tale principio nella misura in cui è possibile farlo includerebbe strategie per ridurre la necessità di futuri cambiamenti?
Jimbo,

@Jimbo, ho aggiornato la mia risposta per cercare di rispondere al tuo commento.
David Arno,

Apprezzo il tuo contributo. BTW. Nemmeno Robert C Martin accetta che il principio Open / Closed abbia una regola dura. È una regola empirica che sarà inevitabilmente infranta. Applicare il principio è un esercizio nel tentativo di rispettarlo il più possibile. Ecco perché ho usato la parola "praticabile" in precedenza.
Jimbo,

Non è inversione di dipendenza passare i parametri dell'utente anziché dell'utente stesso.
James Ellis-Jones,

@ JamesEllis-Jones, l'inversione delle dipendenze inverte le dipendenze da "chiedi", a "dire". Se si passa in Userun'istanza e quindi si interroga quell'oggetto per ottenere un parametro, si stanno invertendo solo parzialmente le dipendenze; c'è ancora qualche domanda da fare. La vera inversione di dipendenza è "dillo, non chiedere" al 100%. Ma ha un prezzo complesso.
David Arno,

10

Sì, la modifica di una funzione esistente costituisce una violazione del principio aperto / chiuso. Stai modificando qualcosa che dovrebbe essere chiuso alla modifica a causa della modifica dei requisiti. Una progettazione migliore (per non cambiare quando cambiano i requisiti) sarebbe quella di passare l'utente a cose che dovrebbero funzionare sugli utenti.

Ma che potrebbe conciliarsi con l'interfaccia segregazione principio, dal momento che si potrebbe essere passando lungo modo più informazioni rispetto alle esigenze di funzionalità per fare il suo lavoro.

Quindi, come con la maggior parte delle cose , dipende .

Usando solo un nome utente, la funzione sarà più flessibile, lavorando con nomi utente indipendentemente da dove provengano e senza la necessità di creare un oggetto utente pienamente funzionante. Fornisce resilienza al cambiamento se si pensa che cambierà la fonte dei dati.

L'uso dell'intero Utente rende più chiaro l'utilizzo e rende un contratto più solido con i suoi chiamanti. Fornisce resilienza al cambiamento se ritieni che sarà necessario un maggior numero di utenti.


+1 ma non sono sicuro della tua frase "potresti passare più informazioni". Quando si passa (Utente l'utente) si passa il minimo di informazioni, un riferimento a un oggetto. È vero che il riferimento può essere utilizzato per ottenere ulteriori informazioni, ma significa che il codice chiamante non deve ottenerlo. Quando passi (GUID userid, string username) il metodo chiamato può sempre chiamare User.find (userid) per trovare l'interfaccia pubblica dell'oggetto, quindi non nascondi nulla.
inizio

5
@dcorking, " Quando passi (Utente dell'utente) passi il minimo di informazioni, un riferimento a un oggetto ". Si passano le informazioni massime relative a quell'oggetto: l'intero oggetto. " il metodo chiamato può sempre chiamare User.find (userid) ...". In un sistema ben progettato, ciò non sarebbe possibile in quanto il metodo in questione non avrebbe accesso User.find(). In effetti non dovrebbe esserci nemmeno un User.find. Trovare un utente non dovrebbe mai essere la responsabilità di User.
David Arno,

2
@dcorking - enh. Che stai passando un riferimento che sembra essere piccolo è una coincidenza tecnica. Stai accoppiando l'intero Useralla funzione. Forse ha senso. Ma forse la funzione dovrebbe interessarsi solo del nome dell'utente e passare cose come la data di iscrizione dell'utente o l'indirizzo non è corretto.
Telastyn,

@DavidArno forse è la chiave per una risposta chiara a OP. Di chi dovrebbe essere la responsabilità di trovare un utente? Esiste un nome per il principio progettuale di separazione del cercatore / fabbrica dalla classe?
inizio

1
@dcorking Direi che è una delle implicazioni del principio di responsabilità singola. Sapere dove sono archiviati gli utenti e come recuperarli tramite ID sono responsabilità separate che una Userclasse non dovrebbe avere. Potrebbe esserci un UserRepositoryo simile che si occupa di tali cose.
Hulk,

3

Questo disegno segue il modello di oggetti parametro . Risolve i problemi che derivano dall'avere molti parametri nella firma del metodo.

Ho ragione nel pensare che si tratti di una violazione del principio Open / Closed?

No. L'applicazione di questo modello abilita il principio Open / close (OCP). Ad esempio, le classi derivate di Userpossono essere fornite come parametro che inducono un comportamento diverso nella classe consumante.

Ci sono altri principi infranti da questa pratica?

Si può accadere. Lasciatemi spiegare sulla base dei principi SOLIDI.

Il principio di responsabilità singola (SRP) può essere violato se ha il design come hai spiegato:

Questa classe espone tutte le informazioni sull'utente, il loro ID, nome, livelli di accesso a ciascun modulo, fuso orario ecc.

Il problema è con tutte le informazioni . Se la Userclasse ha molte proprietà, diventa un enorme oggetto di trasferimento dati che trasporta informazioni non correlate dal punto di vista delle classi consumatrici. Esempio: dal punto di vista di una classe che consuma UserAuthenticationla proprietà User.Ide User.Namesono rilevanti, ma non User.Timezone.

Anche il principio di segregazione dell'interfaccia (ISP) viene violato con un ragionamento simile ma aggiunge un'altra prospettiva. Esempio: supponiamo che una classe di consumo UserManagementrichieda la User.Namedivisione della proprietà User.LastNamee che anche User.FirstNamela classe UserAuthenticationdebba essere modificata per questo.

Fortunatamente l'ISP offre anche una possibile soluzione al problema: di solito tali oggetti parametro o oggetti di trasporto dati iniziano in piccolo e crescono nel tempo. Se questo diventa ingombrante, prendere in considerazione il seguente approccio: introdurre interfacce su misura per le esigenze delle classi consumatrici. Esempio: introdurre interfacce e lasciare che la Userclasse ne derivi:

class User : IUserAuthenticationInfo, IUserLocationInfo { ... }

Ogni interfaccia dovrebbe esporre un sottoinsieme di proprietà correlate della Userclasse necessarie affinché una classe consumatrice possa completare il suo funzionamento. Cerca gruppi di proprietà. Prova a riutilizzare le interfacce. Nel caso della classe che consuma UserAuthenticationusare IUserAuthenticationInfoinvece di User. Quindi, se possibile, suddividere la Userclasse in più classi concrete usando le interfacce come "stencil".


1
Una volta che l'utente si è complicato, c'è ad esempio un'esplosione combinatoria di possibili sottointerfacce, se l'utente ha solo 3 proprietà, ci sono 7 possibili combinazioni. La tua proposta suona bene ma non è realizzabile.
user949300,

1. Analiticamente hai ragione. Tuttavia, a seconda di come viene modellato il dominio, i bit di informazioni correlate tendono a raggrupparsi. Quindi praticamente non è necessario affrontare tutte le possibili combinazioni di interfacce e proprietà. 2. L'approccio delineato non intendeva essere una soluzione universale, ma forse dovrei aggiungere un po 'più "possibile" e "possibile" alla risposta.
Theo Lenndorff,

2

Di fronte a questo problema nel mio codice, ho concluso che le classi / oggetti del modello di base sono la risposta.

Un esempio comune sarebbe il modello di repository. Spesso quando si esegue una query su un database tramite repository, molti dei metodi nel repository accettano molti degli stessi parametri.

Le mie regole empiriche per i repository sono:

  • Laddove più di un metodo accetta gli stessi 2 o più parametri, i parametri devono essere raggruppati come oggetto modello.

  • Laddove un metodo accetta più di 2 parametri, i parametri devono essere raggruppati come oggetto modello.

  • I modelli possono ereditare da una base comune, ma solo quando ha davvero senso (di solito è meglio rifattorizzare più tardi che iniziare con l'eredità in mente).


I problemi con l'utilizzo di modelli da altri livelli / aree non diventano evidenti fino a quando il progetto non diventa un po 'complesso. È solo allora che trovi meno codice che crea più lavoro o più complicazioni.

E sì, è del tutto bene avere 2 modelli diversi con proprietà identiche che servono diversi livelli / scopi (es. ViewModels vs POCO).


2

Controlliamo solo i singoli aspetti di SOLID:

  • Responsabilità unica: possibilmente fregata se le persone tendono a passare solo intorno alle sezioni della classe.
  • Aperto / chiuso: irrilevante dove vengono passate le sezioni della classe, solo dove viene passato l'intero oggetto. (Penso che sia qui che entra in gioco la dissonanza cognitiva: devi cambiare codice lontano ma la classe stessa sembra a posto.)
  • Sostituzione Liskov: senza problemi, non stiamo facendo sottoclassi.
  • Inversione di dipendenza (dipende da astrazioni, non da dati concreti). Sì, è stato violato: le persone non hanno astrazioni, tirano fuori elementi concreti della classe e lo fanno passare. Penso che questo sia il problema principale qui.

Una cosa che tende a confondere l'istinto progettuale è che la classe è essenzialmente per oggetti globali ed essenzialmente di sola lettura. In una situazione del genere, la violazione delle astrazioni non fa molto male: la sola lettura di dati non modificati crea un accoppiamento piuttosto debole; solo quando diventa un mucchio enorme il dolore diventa evidente.
Per ripristinare l'istinto progettuale, supponi solo che l'oggetto non sia molto globale. Quale contesto avrebbe bisogno di una funzione se l' Useroggetto potesse essere mutato in qualsiasi momento? Quali componenti dell'oggetto verrebbero probabilmente mutati insieme? Questi possono essere suddivisi User, sia come oggetto secondario referenziato sia come interfaccia che espone solo una "fetta" di campi correlati non è così importante.

Un altro principio: guarda le funzioni che usano parti di Usere vedi quali campi (attributi) tendono ad andare insieme. Questo è un buon elenco preliminare di oggetti secondari: devi assolutamente pensare se appartengono effettivamente insieme.

È un sacco di lavoro, ed è un po 'difficile da fare, e il tuo codice diventerà leggermente meno flessibile perché diventerà leggermente più difficile identificare l'oggetto secondario (interfaccia secondaria) che deve essere passato a una funzione, in particolare se gli oggetti secondari si sovrappongono.

Dividere Userin realtà diventerà brutto se gli oggetti secondari si sovrappongono, quindi le persone saranno confuse su quale scegliere se tutti i campi richiesti provengono dalla sovrapposizione. Se ti dividi in modo gerarchico (ad es. Hai quello UserMarketSegmentche, tra le altre cose, ha UserLocation), le persone non saranno sicure a quale livello si trova la funzione che stanno scrivendo: si tratta di dati utente a Location livello o a MarketSegmentlivello? Non è esattamente di aiuto che questo possa cambiare nel tempo, vale a dire che sei tornato a cambiare le firme delle funzioni, a volte attraverso un'intera catena di chiamate.

In altre parole: a meno che tu non conosca veramente il tuo dominio e abbia un'idea abbastanza chiara di quale modulo abbia a che fare con quali aspetti User, non vale davvero la pena migliorare la struttura del programma.


1

Questa è una domanda davvero interessante. Dipende.

Se ritieni che il tuo metodo potrebbe cambiare in futuro internamente per richiedere diversi parametri dell'oggetto Utente, dovresti certamente passare l'intero processo. Il vantaggio è che il codice esterno al metodo è quindi protetto dalle modifiche all'interno del metodo in termini di parametri che sta utilizzando, che come dici tu causerebbe una cascata di modifiche esternamente. Quindi il passaggio dell'intero utente aumenta l'incapsulamento.

Se sei abbastanza sicuro di non aver mai bisogno di usare altro che dire l'e-mail dell'Utente, dovresti comunicarlo. Il vantaggio è che puoi quindi utilizzare il metodo in una più ampia gamma di contesti: potresti usarlo per esempio con l'e-mail di un'azienda o con l'e-mail digitata da qualcuno. Ciò aumenta la flessibilità.

Questo fa parte di una più ampia gamma di domande sulla costruzione di classi per avere un ambito ampio o ristretto, incluso se iniettare o meno dipendenze e se avere oggetti disponibili a livello globale. C'è una sfortunata tendenza al momento a pensare che uno scopo più ristretto sia sempre buono. Tuttavia, c'è sempre un compromesso tra incapsulamento e flessibilità come in questo caso.


1

Trovo che sia meglio passare il minor numero di parametri possibile e quanti più necessari. Ciò semplifica i test e non richiede la creazione di interi oggetti.

Nel tuo esempio, se hai intenzione di utilizzare solo l'ID utente o il nome utente, questo è tutto ciò che dovresti passare. Se questo schema si ripete più volte e l'oggetto utente reale è molto più grande, il mio consiglio è quello di creare un'interfaccia (e) più piccola per quello. Potrebbe essere

interface IIdentifieable
{
    Guid ID { get; }
}

o

interface INameable
{
    string Name { get; }
}

Questo rende molto più semplice il test con derisione e sai immediatamente quali sono i valori realmente utilizzati. In caso contrario, spesso è necessario inizializzare oggetti complessi con molte altre dipendenze sebbene alla fine siano necessarie solo una o due proprietà.


1

Ecco qualcosa che ho incontrato di volta in volta:

  • Un metodo accetta un argomento di tipo User(o Productqualsiasi altra cosa) che ha molte proprietà, anche se ne utilizza solo alcune.
  • Per qualche motivo, alcune parti del codice devono chiamare quel metodo anche se non ha un Useroggetto completamente popolato . Crea un'istanza e inizializza solo le proprietà effettivamente necessarie al metodo.
  • Questo succede un sacco di volte.
  • Dopo un po ', quando incontri un metodo che ha un Userargomento, ti ritrovi a dover trovare chiamate a quel metodo per trovare da dove Userproviene in modo da sapere quali proprietà sono popolate. È un utente "reale" con un indirizzo e-mail o è stato appena creato per passare un ID utente e alcune autorizzazioni?

Se si crea Usere si popolano solo poche proprietà perché sono quelle di cui il metodo ha bisogno, il chiamante conosce davvero più i meccanismi interni del metodo di quanto dovrebbe.

Ancora peggio, quando ne hai un'istanza User, devi sapere da dove proviene in modo da sapere quali proprietà sono popolate. Non devi saperlo.

Nel tempo, quando gli sviluppatori vedono Userusato come contenitore per argomenti di metodo, potrebbero iniziare ad aggiungere proprietà ad esso per scenari monouso. Ora sta diventando brutto, perché la classe è ingombra di proprietà che saranno quasi sempre nulle o predefinite.

Tale corruzione non è inevitabile, ma accade ripetutamente quando passiamo un oggetto in giro solo perché abbiamo bisogno di accedere ad alcune delle sue proprietà. La zona di pericolo è la prima volta che vedi qualcuno che crea un'istanza Usere popola solo poche proprietà in modo che possa passarla a un metodo. Metti il ​​piede su di esso perché è un sentiero oscuro.

Ove possibile, imposta l'esempio giusto per il prossimo sviluppatore passando solo ciò che devi passare.

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.