È problematico avere una dipendenza tra oggetti dello stesso livello in un'architettura software a più livelli?


12

Considerando un software di dimensioni medio-grandi con architettura a n layer e iniezione di dipendenza, sono a mio agio nel dire che un oggetto appartenente a un layer può dipendere da oggetti di layer inferiori ma mai da oggetti di layer superiori.

Ma non sono sicuro di cosa pensare degli oggetti che dipendono da altri oggetti dello stesso livello.

Ad esempio, supponiamo un'applicazione con tre livelli e diversi oggetti come quello nell'immagine. Ovviamente le dipendenze top-down (frecce verdi) sono ok, bottom-up (freccia rossa) non sono ok, ma che dire di una dipendenza all'interno dello stesso layer (freccia gialla)?

inserisci qui la descrizione dell'immagine

Escludendo la dipendenza circolare, sono curioso di sapere qualsiasi altro problema possa sorgere e quanto l'architettura a strati venga violata in questo caso.


Se non hai cicli, in un livello in cui hai i collegamenti orizzontali esiste una suddivisione in sottostrati che ha solo dipendenze tra i livelli
max630

Risposte:


10

Sì, gli oggetti in un livello possono avere dipendenze dirette tra loro, a volte anche cicliche - questo è in realtà ciò che fa la differenza principale rispetto alle dipendenze consentite tra oggetti in livelli diversi, in cui non sono consentite dipendenze dirette o solo una dipendenza rigorosa direzione.

Tuttavia, ciò non significa che dovrebbero avere tali dipendenze in modo arbitrario. Dipende in realtà da ciò che rappresentano i livelli, da quanto è grande il sistema e da quale responsabilità dovrebbe avere la parte. Si noti che "architettura stratificata" è un termine vago, c'è una grande variazione di ciò che effettivamente significa in diversi tipi di sistemi.

Ad esempio, supponiamo che tu abbia un "sistema a livelli orizzontali", con un livello di database, un livello aziendale e un livello di interfaccia utente (UI). Diciamo che il livello dell'interfaccia utente contiene almeno diverse dozzine di classi di dialogo diverse.

Si può scegliere un disegno in cui nessuna delle classi di dialogo dipenda direttamente da un'altra classe di dialogo. Si può scegliere un disegno in cui esistono "finestre di dialogo principali" e "finestre di dialogo secondarie" e esistono solo dipendenze dirette dalle finestre di dialogo "principali" a "secondarie". Oppure si può preferire una progettazione in cui qualsiasi classe UI esistente può utilizzare / riutilizzare qualsiasi altra classe UI dallo stesso livello.

Queste sono tutte possibili scelte di progettazione, forse più o meno sensate a seconda del tipo di sistema che stai costruendo, ma nessuna di esse rende invalida la "stratificazione" del tuo sistema.


Continuando sull'esempio dell'interfaccia utente, quali sono i pro e i contro di avere le dipendenze interne? Vedo che facilita il riutilizzo e l'introduzione di dipendenze cicliche, il che potrebbe costituire un problema a seconda del metodo DI utilizzato. Qualunque altra cosa?
bracco23,

@ bracco23: i pro ei contro di avere dipendenze "interne" sono gli stessi dei pro dei contro di avere dipendenze arbitrarie. I "contro" sono che rendono i componenti più difficili da riutilizzare e più difficili da testare, soprattutto in isolamento da altri componenti. I pro sono, le dipendenze esplicite rendono le cose che richiedono la coesione più facili da usare, comprendere, gestire e testare.
Doc Brown,

14

Sono a mio agio nel dire che un oggetto appartenente a un livello può dipendere da oggetti provenienti da livelli inferiori

Ad essere sincero, non credo che dovresti sentirti a tuo agio con questo. Quando ho a che fare con qualcosa che non sia un sistema banale, mirerei a garantire che tutti i livelli dipendano sempre e soltanto dalle astrazioni dagli altri livelli; sia inferiore che superiore.

Quindi, per esempio, Obj 1non dovrebbe dipendere da Obj 3. Dovrebbe dipendere, ad esempio, IObj 3e dovrebbe essere detto con quale implementazione di quell'astrazione dovrà lavorare in fase di esecuzione. La cosa che fa il racconto non dovrebbe essere correlata a nessuno dei livelli poiché il suo compito è mappare quelle dipendenze. Potrebbe essere un contenitore IoC, un codice personalizzato chiamato ad es. mainChe utilizza DI puro. O a una spinta potrebbe anche essere un localizzatore di servizi. Indipendentemente da ciò, le dipendenze non esistono tra i livelli fino a quando quella cosa non fornisce la mappatura.

Ma non sono sicuro di cosa pensare degli oggetti che dipendono da altri oggetti dello stesso livello.

Direi che questa è l'unica volta in cui dovresti avere dipendenze dirette. Fa parte del funzionamento interno di quello strato e può essere modificato senza influenzare altri strati. Quindi non è un accoppiamento dannoso.


Grazie per la risposta. Sì, il layer non dovrebbe esporre oggetti ma interfacce, lo so ma l'ho lasciato fuori per motivi di semplicità.
bracco23,

4

Vediamo questo praticamente

inserisci qui la descrizione dell'immagine

Obj 3ora sa che Obj 4esiste. E allora? Perché ci importa?

Dice DIP

"I moduli di alto livello non dovrebbero dipendere da moduli di basso livello. Entrambi dovrebbero dipendere da astrazioni."

OK ma non sono tutte le astrazioni degli oggetti?

DIP dice anche

"Le astrazioni non dovrebbero dipendere dai dettagli. I dettagli dovrebbero dipendere dalle astrazioni."

OK ma, se il mio oggetto è incapsulato correttamente, non nasconde alcun dettaglio?

Ad alcune persone piace insistere ciecamente sul fatto che ogni oggetto necessita di un'interfaccia per parole chiave. Non sono uno di loro. Mi piace insistere ciecamente sul fatto che se non li userai ora hai bisogno di un piano per affrontare il bisogno di qualcosa di simile in seguito.

Se il tuo codice è completamente refactable in ogni versione, puoi estrarre le interfacce in seguito se ne hai bisogno. Se hai pubblicato un codice che non vuoi ricompilare e ti ritrovi a desiderare di parlare attraverso un'interfaccia, avrai bisogno di un piano.

Obj 3sa che Obj 4esiste. Ma Obj 3sa se Obj 4è concreto?

Questo è il motivo per cui è così bello NON diffondersi newovunque. Se Obj 3non sa se Obj 4è concreto, probabilmente perché non lo ha creato, allora se ti intrufolassi più tardi e ti trasformassi Obj 4in una classe astratta Obj 3, non ti importerebbe.

Se riesci a farlo, allora Obj 4è stato completamente astratto per tutto il tempo. L'unica cosa che ti fa ottenere un'interfaccia tra loro dall'inizio è la certezza che qualcuno non aggiungerà accidentalmente codice che regala che Obj 4è concreto in questo momento. I costruttori protetti possono mitigare questo rischio ma ciò porta a un'altra domanda:

Obj 3 e Obj 4 sono nello stesso pacchetto?

Gli oggetti sono spesso raggruppati in qualche modo (pacchetto, spazio dei nomi, ecc.). Quando raggruppati con saggezza, cambia gli impatti più probabili all'interno di un gruppo piuttosto che tra i gruppi.

Mi piace raggruppare per funzione. Se Obj 3e Obj 4fanno parte dello stesso gruppo e dello stesso livello, è molto improbabile che tu ne abbia pubblicato uno e non voglialo rifattorizzare mentre devi cambiare solo l'altro. Ciò significa che è meno probabile che questi oggetti traggano beneficio dall'aver messo un'astrazione tra di loro prima che abbia un chiaro bisogno.

Se stai attraversando un confine di gruppo, è davvero una buona idea lasciare che gli oggetti su entrambi i lati possano variare indipendentemente.

Dovrebbe essere così semplice ma sfortunatamente sia Java che C # hanno fatto scelte sfortunate che complicano questo.

In C # è tradizione nominare ogni interfaccia di parole chiave con un Iprefisso. Ciò costringe i clienti a SAPERE che stanno parlando con un'interfaccia di parole chiave. Ciò è in disordine con il piano di refactoring.

In Java è tradizione utilizzare un modello di denominazione migliore: FooImple implements Footuttavia, ciò aiuta solo a livello di codice sorgente poiché Java compila le interfacce delle parole chiave in un binario diverso. Ciò significa che quando si Fooesegue il refactoring da client concreti a astratti che non necessitano di un singolo carattere di codice modificato, è comunque necessario ricompilarli.

Sono questi BUG in questi particolari linguaggi che impediscono alle persone di rimandare l'astrattismo formale fino a quando non ne hanno davvero bisogno. Non hai detto quale lingua stai usando, ma hai capito che ci sono alcune lingue che semplicemente non hanno questi problemi.

Non hai detto quale lingua stai usando, quindi ti esorto ad analizzare attentamente la tua lingua e la tua situazione prima di decidere che saranno interfacce di parole chiave ovunque.

Il principio YAGNI svolge qui un ruolo chiave. Ma anche "Per favore, rendimi difficile spararmi al piede".


0

Oltre alle risposte di cui sopra, penso che potrebbe aiutarti a guardarlo da diversi punti di vista.

Ad esempio, dal punto di vista della regola di dipendenza . DR è una regola suggerita da Robert C. Martin per la sua famosa architettura pulita .

Dice

Le dipendenze del codice sorgente devono puntare solo verso l'interno, verso politiche di livello superiore.

Per politiche di livello superiore , intende un livello più alto di astrazioni. Componenti che perdono nei dettagli dell'implementazione, come ad esempio interfacce o classi astratte in contrasto con classi concrete o strutture di dati.

Il fatto è che la regola non è limitata alla dipendenza inter-layer. Indica solo la dipendenza tra parti di codice, indipendentemente dalla posizione o dal livello a cui appartengono.

Quindi no, non c'è nulla di intrinsecamente sbagliato nell'avere dipendenze tra elementi dello stesso livello. Tuttavia, la dipendenza può ancora essere implementata per comunicare con il principio di dipendenza stabile .

Un altro punto di vista è SRP.

Il disaccoppiamento è il nostro modo di rompere le dipendenze dannose e comunicare con alcune buone pratiche come l'inversione delle dipendenze (IoC). Tuttavia, quegli elementi che condividono i motivi del cambiamento non danno ragioni per il disaccoppiamento perché gli elementi con lo stesso motivo del cambiamento cambieranno allo stesso tempo (molto probabilmente) e saranno distribuiti allo stesso tempo. Se questo è il caso tra Obj3e Obj4poi, ancora una volta, non c'è nulla di intrinsecamente sbagliato.

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.