È una cattiva pratica passare le istanze attraverso più livelli?


60

Nella progettazione del mio programma, arrivo spesso al punto in cui devo passare istanze di oggetti attraverso diverse classi. Ad esempio, se ho un controller che carica un file audio e quindi lo passa a un lettore e il lettore lo passa al playerRunnable, che lo passa di nuovo da qualche altra parte ecc. Sembra un po 'male, ma non lo faccio sapere come evitarlo. O va bene farlo?

EDIT: Forse l'esempio del lettore non è il migliore perché potrei caricare il file in un secondo momento, ma in altri casi non funziona.

Risposte:


54

Come altri hanno già detto, questa non è necessariamente una cattiva pratica, ma dovresti prestare attenzione a non spezzare la separazione delle preoccupazioni dei livelli e passare istanze specifiche dei livelli tra i livelli. Per esempio:

  • Gli oggetti del database non devono mai essere passati a livelli superiori. Ho visto programmi che utilizzavano la classe DataAdapter di .NET , una classe di accesso al DB e la passavano al livello dell'interfaccia utente, piuttosto che usare il DataAdapter nel DAL, creando un DTO o un set di dati e passando quello. L'accesso al DB è il dominio del DAL.
  • Gli oggetti UI dovrebbero ovviamente essere limitati al livello UI. Ancora una volta, l'ho visto violato, sia con ListBox popolati con i dati dell'utente passati al livello BL, anziché con un array / DTO del suo contenuto, e (un mio preferito in particolare), una classe DAL che ha recuperato dati gerarchici da il DB, invece di restituire una struttura di dati gerarchica, ha appena creato e popolato un oggetto TreeView e lo ha restituito all'interfaccia utente per essere aggiunto dinamicamente a un modulo.

Tuttavia, se le istanze che stai passando sono le DTO o le entità stesse, probabilmente va bene.


1
Questo potrebbe sembrare scioccante, ma nei primi giorni bui di .NET questa era la pratica generalmente raccomandata ed era probabilmente migliore di quella che fanno la maggior parte degli altri stack.
Wyatt Barnett,

1
Non sono d'accordo. È vero che Microsoft ha approvato la pratica delle app a livello singolo in cui anche il client Winforms accedeva al DB e DataAdapter è stato aggiunto direttamente al modulo come controllo invisibile, ma si tratta solo di un'architettura specifica, diversa dall'impostazione di livello N dell'OP . Ma in un'architettura multistrato, e questo era vero per VB6 / DNA anche prima di .NET, gli oggetti DB rimanevano nel livello DB.
Avner Shahar-Kashtan,

Per chiarire: hai visto persone accedere direttamente all'interfaccia utente (nel tuo esempio, caselle di riepilogo) dal loro "Livello dati"? Non credo di aver riscontrato una tale violazione nel codice di produzione ... wow ..
Simon Whitehead,

7
@SimonWhitehead Esattamente. Le persone che erano indistinte per la distinzione tra un ListBox e un array e usavano ListBox come DTO. È stato un momento che mi ha aiutato a capire quante ipotesi invisibili che faccio che non sono intuitive per gli altri.
Avner Shahar-Kashtan,

1
@SimonWhitehead - Sì, ultimamente ho visto che nei programmi VB6 e VB.NET Framework 1.1 e 2.0 e sono stato incaricato di mantenere tali mostri. Diventa molto brutto, molto rapidamente.
jfrankcarr,

15

Interessante che nessuno abbia ancora parlato di oggetti immutabili . Direi che passare un oggetto immutabile in giro attraverso tutti i vari strati è in realtà una cosa positiva piuttosto che creare molti oggetti di breve durata per ogni strato.

Ci sono alcune grandi discussioni sull'immutabilità di Eric Lippert sul suo blog

D'altra parte, direi che passare oggetti mutabili tra i livelli è un cattivo design. Stai essenzialmente costruendo un livello con la promessa che i livelli circostanti non lo modificheranno in modo da violare il tuo codice.


13

Passare istanze di oggetti in giro è una cosa normale da fare. Riduce la necessità di mantenere lo stato (ovvero le variabili di istanza) e disaccoppia il codice dal suo contesto di esecuzione.

Un problema che potresti dover affrontare è il refactoring, quando è necessario modificare le firme di più metodi lungo la catena di invocazione in risposta alla modifica dei requisiti dei parametri di un metodo vicino alla parte inferiore di quella catena. Tuttavia, può essere mitigato con l'uso di moderni strumenti di sviluppo software che aiutano nel refactoring.


6
Direi che un altro problema che potresti incontrare riguarda l'immutabilità. Ricordo un bug molto imbarazzante in un progetto a cui ho lavorato in cui uno sviluppatore ha modificato un DTO senza pensare al fatto che la sua classe particolare non era l' unica con un riferimento a quell'oggetto.
Phil

8

Forse minore, ma c'è il rischio di assegnare questo riferimento da qualche parte in uno dei livelli potenzialmente causando un riferimento penzolante o una perdita di memoria in seguito.


Il tuo punto è corretto, ma dalla terminologia di OP ("passaggio di istanze di oggetti") ho la sensazione che o stia passando valori (non puntatori) o che stesse parlando di un ambiente raccolto dalla spazzatura (Java, C #, Python, Go,. ..).
Mohammad Dehghan,

7

Se si passano oggetti in giro semplicemente perché è necessario in un'area remota del codice, l'utilizzo dell'inversione dei modelli di progettazione di iniezione di controllo e dipendenza insieme a un contenitore IoC appropriato può risolvere in modo corretto i problemi relativi al trasporto di istanze di oggetti. L'ho usato su un progetto di medie dimensioni e non avrei mai più pensato di scrivere un grosso codice di server senza usarlo.


Sembra interessante, uso già l'iniezione del costruttore e penso che i miei componenti di alto livello controllino i componenti di basso livello. Come si usa un contenitore IOC per evitare il trasporto di istanze?
Puckl,

Penso di aver trovato la risposta qui: martinfowler.com/articles/injection.html
Puckl

1
Nota a margine, se stai lavorando in Java, Guice è davvero bello e puoi adattare i tuoi binding a cose come richieste in modo che il componente di alto livello stia creando l'ambito e legando le istanze alle classi corrette in quel momento.
Dave,

4

Passare i dati attraverso un mucchio di livelli non è una brutta cosa, è davvero l'unico modo in cui un sistema a strati può funzionare senza violare la struttura a strati. Il segno che ci sono problemi è quando si passano i dati a più oggetti nello stesso livello per raggiungere il proprio obiettivo.


3

Risposta rapida: non c'è nulla di sbagliato nel passare istanze di oggetti. Come anche accennato, il punto è saltare l'assegnazione di questo riferimento in tutti i livelli potenzialmente causando un riferimento penzolante o perdite di memoria.

Nei nostri progetti, usiamo questa pratica per passare DTO (oggetto di trasferimento dati) tra i livelli ed è una pratica molto utile. Riutilizziamo anche i nostri oggetti dto per costruire una volta più complessi, ad esempio per informazioni di riepilogo.


3

Sono principalmente uno sviluppatore dell'interfaccia utente Web, ma mi sembra che il tuo disagio intuitivo potrebbe essere meno sul pass-through dell'istanza e più sul fatto che stai andando un po 'procedurale con quel controller. Il tuo controller dovrebbe sudare tutti questi dettagli? Perché fa riferimento anche a più di un altro oggetto per riprodurre l'audio?

Nella progettazione di OOP, tendo a pensare in termini di ciò che è sempreverde e ciò che è più probabile che sia soggetto a cambiamenti. L'oggetto del cambiamento è ciò che vorresti tendere a mettere nelle caselle degli oggetti più grandi in modo da poter mantenere interfacce coerenti anche quando i giocatori cambiano o vengono aggiunte nuove opzioni. Oppure ti ritrovi a voler scambiare oggetti audio o componenti al loro interno all'ingrosso.

In questo caso, il controller deve identificare la necessità di riprodurre un file audio e quindi disporre di un modo coerente / sempreverde per riprodurlo. Le cose del lettore audio d'altra parte potrebbero facilmente cambiare man mano che la tecnologia e le piattaforme vengono alterate o vengono aggiunte nuove scelte. Tutti questi dettagli dovrebbero trovarsi sotto un'interfaccia di un oggetto composito più grande, IMO, e non dovresti riscrivere il tuo controller quando cambiano i dettagli su come viene riprodotto l'audio. Quindi quando si passa un'istanza di un oggetto con i dettagli come la posizione del file nell'oggetto più grande, tutto ciò che si scambia viene fatto all'interno di un contesto appropriato in cui è meno probabile che qualcuno faccia qualcosa di stupido con esso.

Quindi, in questo caso, non penso che sia l'istanza di oggetto che viene lanciata in giro che potrebbe infastidirti. È che il Capitano Picard sta correndo verso la sala macchine per accendere il nucleo di curvatura, correndo di nuovo sul ponte per tracciare le coordinate e quindi premere il pulsante "punch-it" dopo aver acceso gli scudi invece di dire semplicemente "Prendi noi sul pianeta X in Warp 9. Rendilo così ". e lasciando che il suo equipaggio risolvesse i dettagli. Perché quando lo gestisce in questo modo, può comandare qualsiasi nave della flotta senza conoscere la disposizione di ogni nave e come funziona tutto. E questa è in definitiva la più grande vittoria del design OOP a cui puntare, IMO.


2

È un progetto abbastanza comune che finisce per accadere anche se potresti avere problemi di latenza se la tua app è sensibile a quel genere di cose.


2

Questo problema può essere risolto con variabili con ambito dinamico, se la tua lingua li ha, o archiviazione locale thread. Questi meccanismi ci consentono di associare alcune variabili personalizzate a una catena di attivazione o thread di controllo, in modo da non dover passare questi valori nel codice che non ha nulla a che fare con loro solo in modo che possano essere comunicati a qualche altro codice che ne ha bisogno.


2

Come hanno sottolineato le altre risposte, questo non è un progetto intrinsecamente scadente. Può creare un accoppiamento stretto tra le classi nidificate e quelle che le annidano, ma allentando l'accoppiamento potrebbe non essere un'opzione valida se l'annidamento dei riferimenti fornisce un valore al progetto.

Una possibile soluzione è "appiattire" i riferimenti nidificati nella classe controller.

Invece di passare più volte un parametro attraverso oggetti nidificati, è possibile mantenere nella classe controller riferimenti a tutti gli oggetti nidificati.

Come esattamente questo sia implementato (o se è anche una soluzione valida) dipende dalla progettazione attuale del sistema, come ad esempio:

  • Sei in grado di mantenere una sorta di mappa degli oggetti nidificati nel controller senza che diventi troppo complicato?
  • Quando si passa il parametro all'oggetto nidificato appropriato, l'oggetto nidificato può riconoscere immediatamente il parametro o si sono verificate funzionalità aggiuntive durante il passaggio attraverso gli oggetti nidificati?
  • eccetera.

Questo è un problema che ho riscontrato in un modello di progettazione MVC per un client GXT. I nostri componenti della GUI contenevano componenti della GUI nidificati per diversi livelli. Quando i dati del modello sono stati aggiornati, abbiamo finito per passarli attraverso i diversi livelli fino a raggiungere i componenti appropriati. Ha creato un accoppiamento indesiderato tra i componenti della GUI perché se volevamo che una nuova classe di componenti della GUI accettasse i dati del modello, dovevamo creare metodi per aggiornare i dati del modello in tutti i componenti della GUI che contenevano la nuova classe.

Per risolverlo, abbiamo mantenuto nella classe Visualizza una mappa di riferimenti a tutti i componenti della GUI nidificati in modo che ogni volta che i dati del modello venivano aggiornati, la Vista poteva inviare i dati del modello aggiornati direttamente ai componenti della GUI che ne avevano bisogno, fine della storia . Funzionava bene perché c'erano solo singole istanze di ciascun componente della GUI. Potrei vederlo non funzionare così bene se ci fossero più istanze di alcuni componenti della GUI, rendendo difficile identificare quale copia doveva essere aggiornata.


0

Quello che stai descrivendo è un modello di progettazione chiamato Catena di responsabilità . Apple utilizza questo modello per il proprio sistema di gestione degli eventi, per quello che vale.

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.