Perché è una buona idea che i livelli di applicazione "inferiori" non siano a conoscenza di quelli "più alti"?

66

In un'app Web MVC tipica (ben progettata), il database non è a conoscenza del codice del modello, il codice del modello non è a conoscenza del codice del controller e il codice del controller non è a conoscenza del codice di visualizzazione. (Immagino che potresti persino iniziare fino all'hardware, o forse anche oltre, e il pattern potrebbe essere lo stesso.)

Andando nella direzione opposta, puoi scendere di un solo livello. La vista può essere a conoscenza del controller ma non del modello; il controllore può essere a conoscenza del modello ma non del database; il modello può essere a conoscenza del database ma non del sistema operativo. (Qualsiasi cosa più profonda è probabilmente irrilevante.)

Posso intuire intuitivamente perché questa è una buona idea, ma non riesco ad articolarla. Perché questo stile unidirezionale di stratificazione è una buona idea?

    
posta Jason Swett 20.05.2013 - 20:02
fonte

13 risposte

120

I livelli, i moduli, anzi l'architettura stessa, sono mezzi per rendere i programmi per computer più facili da capire per gli esseri umani . Il metodo numericamente ottimale per risolvere un problema è quasi sempre un miscuglio ingarbugliato di codici non modulari, autoreferenziali o addirittura auto-modificanti - sia che si tratti di codice assembler strongmente ottimizzato in sistemi embedded con vincoli di memoria paralizzanti o sequenze di DNA dopo milioni di anni di pressione selettiva. Tali sistemi non hanno strati, nessuna direzione discernibile del flusso di informazioni, in realtà nessuna struttura che possiamo discernere. Per tutti, tranne il loro autore, sembrano funzionare per pura magia.

Nell'ingegneria del software, vogliamo evitarlo. Una buona architettura è una decisione deliberata di sacrificare un po 'di efficienza per rendere il sistema comprensibile dalle persone normali. Capire una cosa alla volta è più facile che capire due cose che hanno senso solo se usate insieme. Ecco perché i moduli e i livelli sono una buona idea.

Ma inevitabilmente i moduli devono chiamare le funzioni l'uno dall'altro, e i livelli devono essere creati uno sopra l'altro. Quindi, in pratica, è sempre necessario costruire sistemi in modo che alcune parti richiedano altre parti. Il compromesso preferito è costruirli in modo tale che una parte ne richieda un'altra, ma quella parte non richiede il primo indietro. E questo è esattamente ciò che ci offre la stratificazione unidirezionale: è possibile comprendere lo schema del database senza conoscere le regole aziendali e comprendere le regole aziendali senza conoscere l'interfaccia utente. Sarebbe bello avere l'indipendenza in entrambe le direzioni - permettere a qualcuno di programmare una nuova interfaccia utente senza conoscere qualsiasi cosa sulle regole aziendali - ma in pratica ciò non è praticamente mai possibile. Le regole pratiche come "Nessuna dipendenza ciclica" o "Le dipendenze devono raggiungere solo un livello" semplicemente catturano il limite praticamente realizzabile dell'idea fondamentale che una cosa alla volta è più facile da capire rispetto a due cose.

    
risposta data 20.05.2013 - 20:30
fonte
61

La motivazione fondamentale è questa: vuoi essere in grado di estrarre un intero livello e sostituire un altro completamente (riscritto) e NESSUNO DOVREBBE ESSERE NOTATO ALLA DIFFERENZA.

L'esempio più ovvio è quello di estrarre il livello inferiore e sostituirne uno diverso. Questo è quello che fai quando sviluppi il / i livello / i superiore / i contro una simulazione dell'hardware, e poi sostituisci l'hardware reale.

Il prossimo esempio è quando estrai un livello intermedio e sostituisci un altro livello intermedio. Considera un'applicazione che utilizza un protocollo eseguito su RS-232. Un giorno, devi cambiare completamente la codifica del protocollo, perché "qualcos'altro è cambiato". (Esempio: passaggio da codifica ASCII diritta a codifica Reed-Solomon di flussi ASCII, perché stavi lavorando su un collegamento radio dal centro di Los Angeles a Marina Del Rey, e ora stai lavorando su un collegamento radio dal centro di Los Angeles a una sonda in orbita Europa , una delle lune di Giove, e quel collegamento ha bisogno di una correzione degli errori in avanti molto migliore.)

L'unico modo per farlo funzionare è se ogni livello esporta un'interfaccia nota e definita sul livello precedente e si aspetta un'interfaccia nota e definita per il livello sottostante.

Ora, non è esattamente il caso che gli strati inferiori non conoscano NIENTE sugli strati superiori. Piuttosto, quello che lo strato inferiore sa è che lo strato immediatamente sopra funzionerà esattamente in base alla sua interfaccia definita. Non può sapere altro, perché per definizione tutto ciò che non è nell'interfaccia definita è soggetto a modifiche SENZA PREAVVISO.

Il layer RS-232 non sa se sta eseguendo ASCII, Reed-Solomon, Unicode (code page araba, code page giapponese, tabella codici Rigellian Beta) o cosa. Sa solo che sta ottenendo una sequenza di byte e sta scrivendo quei byte su una porta. La prossima settimana potrebbe avere una sequenza di byte completamente diversa da qualcosa di completamente diverso. A lui non interessa Si sposta solo byte.

La prima (e migliore) spiegazione del design a strati è la carta classica di Dijkstra "Struttura del IL sistema di multiprogrammazione ". È obbligatorio leggere in questo settore.

    
risposta data 20.05.2013 - 20:46
fonte
8

Perché i livelli più alti potrebbero cambieranno.

Quando ciò accade, sia a causa di cambiamenti di requisiti, nuovi utenti, tecnologia diversa, un'applicazione modulare (vale a dire unidirezionale) dovrebbe richiedere meno manutenzione ed essere più facilmente adattabile alle nuove esigenze.

    
risposta data 20.05.2013 - 20:25
fonte
4

Penso che la ragione principale sia che rende le cose più strettamente accoppiate. Più stretto è l'accoppiamento, più è probabile che si verifichi più tardi. Vedi questo articolo più informazioni: Coupling

Ecco un estratto:

Disadvantages

Tightly coupled systems tend to exhibit the following developmental characteristics, which are often seen as disadvantages: A change in one module usually forces a ripple effect of changes in other modules. Assembly of modules might require more effort and/or time due to the increased inter-module dependency. A particular module might be harder to reuse and/or test because dependent modules must be included.

Con ciò detto sulla ragione di avere un sistema accoppiato teso è per ragioni di prestazioni. Anche l'articolo che ho citato ha alcune informazioni su questo.

    
risposta data 20.05.2013 - 20:59
fonte
4

IMO, è molto semplice. Non puoi riutilizzare qualcosa che rimandi al contesto in cui è usato.

    
risposta data 21.05.2013 - 04:10
fonte
4
___ qstnhdr ___ Perché è una buona idea che i livelli di applicazione "inferiori" non siano a conoscenza di quelli "più alti"? ______ qstntxt ___

In un'app Web MVC tipica (ben progettata), il database non è a conoscenza del codice del modello, il codice del modello non è a conoscenza del codice del controller e il codice del controller non è a conoscenza del codice di visualizzazione. (Immagino che potresti persino iniziare fino all'hardware, o forse anche oltre, e il pattern potrebbe essere lo stesso.)

Andando nella direzione opposta, puoi scendere di un solo livello. La vista può essere a conoscenza del controller ma non del modello; il controllore può essere a conoscenza del modello ma non del database; il modello può essere a conoscenza del database ma non del sistema operativo. (Qualsiasi cosa più profonda è probabilmente irrilevante.)

Posso intuire intuitivamente perché questa è una buona idea, ma non riesco ad articolarla. Perché questo stile unidirezionale di stratificazione è una buona idea?

    
______ azszpr198786 ___

I livelli, i moduli, anzi l'architettura stessa, sono mezzi per rendere i programmi per computer più facili da capire per gli esseri umani . Il metodo numericamente ottimale per risolvere un problema è quasi sempre un miscuglio ingarbugliato di codici non modulari, autoreferenziali o addirittura auto-modificanti - sia che si tratti di codice assembler strongmente ottimizzato in sistemi embedded con vincoli di memoria paralizzanti o sequenze di DNA dopo milioni di anni di pressione selettiva. Tali sistemi non hanno strati, nessuna direzione discernibile del flusso di informazioni, in realtà nessuna struttura che possiamo discernere. Per tutti, tranne il loro autore, sembrano funzionare per pura magia.

Nell'ingegneria del software, vogliamo evitarlo. Una buona architettura è una decisione deliberata di sacrificare un po 'di efficienza per rendere il sistema comprensibile dalle persone normali. Capire una cosa alla volta è più facile che capire due cose che hanno senso solo se usate insieme. Ecco perché i moduli e i livelli sono una buona idea.

Ma inevitabilmente i moduli devono chiamare le funzioni l'uno dall'altro, e i livelli devono essere creati uno sopra l'altro. Quindi, in pratica, è sempre necessario costruire sistemi in modo che alcune parti richiedano altre parti. Il compromesso preferito è costruirli in modo tale che una parte ne richieda un'altra, ma quella parte non richiede il primo indietro. E questo è esattamente ciò che ci offre la stratificazione unidirezionale: è possibile comprendere lo schema del database senza conoscere le regole aziendali e comprendere le regole aziendali senza conoscere l'interfaccia utente. Sarebbe bello avere l'indipendenza in entrambe le direzioni - permettere a qualcuno di programmare una nuova interfaccia utente senza conoscere qualsiasi cosa sulle regole aziendali - ma in pratica ciò non è praticamente mai possibile. Le regole pratiche come "Nessuna dipendenza ciclica" o "Le dipendenze devono raggiungere solo un livello" semplicemente catturano il limite praticamente realizzabile dell'idea fondamentale che una cosa alla volta è più facile da capire rispetto a due cose.

    
______ azszpr198788 ___

La motivazione fondamentale è questa: vuoi essere in grado di estrarre un intero livello e sostituire un altro completamente (riscritto) e NESSUNO DOVREBBE ESSERE NOTATO ALLA DIFFERENZA.

L'esempio più ovvio è quello di estrarre il livello inferiore e sostituirne uno diverso. Questo è quello che fai quando sviluppi il / i livello / i superiore / i contro una simulazione dell'hardware, e poi sostituisci l'hardware reale.

Il prossimo esempio è quando estrai un livello intermedio e sostituisci un altro livello intermedio. Considera un'applicazione che utilizza un protocollo eseguito su RS-232. Un giorno, devi cambiare completamente la codifica del protocollo, perché "qualcos'altro è cambiato". (Esempio: passaggio da codifica ASCII diritta a codifica Reed-Solomon di flussi ASCII, perché stavi lavorando su un collegamento radio dal centro di Los Angeles a Marina Del Rey, e ora stai lavorando su un collegamento radio dal centro di Los Angeles a una sonda in orbita Europa , una delle lune di Giove, e quel collegamento ha bisogno di una correzione degli errori in avanti molto migliore.)

L'unico modo per farlo funzionare è se ogni livello esporta un'interfaccia nota e definita sul livello precedente e si aspetta un'interfaccia nota e definita per il livello sottostante.

Ora, non è esattamente il caso che gli strati inferiori non conoscano NIENTE sugli strati superiori. Piuttosto, quello che lo strato inferiore sa è che lo strato immediatamente sopra funzionerà esattamente in base alla sua interfaccia definita. Non può sapere altro, perché per definizione tutto ciò che non è nell'interfaccia definita è soggetto a modifiche SENZA PREAVVISO.

Il layer RS-232 non sa se sta eseguendo ASCII, Reed-Solomon, Unicode (code page araba, code page giapponese, tabella codici Rigellian Beta) o cosa. Sa solo che sta ottenendo una sequenza di byte e sta scrivendo quei byte su una porta. La prossima settimana potrebbe avere una sequenza di byte completamente diversa da qualcosa di completamente diverso. A lui non interessa Si sposta solo byte.

La prima (e migliore) spiegazione del design a strati è la carta classica di Dijkstra "Struttura del IL sistema di multiprogrammazione ". È obbligatorio leggere in questo settore.

    
______ azszpr198784 ___

Perché i livelli più alti potrebbero cambieranno.

Quando ciò accade, sia a causa di cambiamenti di requisiti, nuovi utenti, tecnologia diversa, un'applicazione modulare (vale a dire unidirezionale) dovrebbe richiedere meno manutenzione ed essere più facilmente adattabile alle nuove esigenze.

    
______ azszpr198795 ___

Penso che la ragione principale sia che rende le cose più strettamente accoppiate. Più stretto è l'accoppiamento, più è probabile che si verifichi più tardi. Vedi questo articolo più informazioni: Coupling

Ecco un estratto:

%bl0ck_qu0te%

Con ciò detto sulla ragione di avere un sistema accoppiato teso è per ragioni di prestazioni. Anche l'articolo che ho citato ha alcune informazioni su questo.

    
______ azszpr198829 ___

IMO, è molto semplice. Non puoi riutilizzare qualcosa che rimandi al contesto in cui è usato.

    
___ ______ azszpr198793 ___

Vorrei aggiungere i miei due centesimi a ciò che Matt Fenwick e Kilian Foth hanno già spiegato.

Un principio dell'architettura del software è che i programmi complessi dovrebbero essere costruiti componendo blocchi più piccoli e autosufficienti (scatole nere): questo riduce al minimo le dipendenze riducendo così la complessità. Quindi, questa dipendenza unidirezionale è una buona idea perché rende più semplice la comprensione del software e la gestione della complessità è uno dei problemi più importanti nello sviluppo del software.

Quindi, in un'architettura a strati, i livelli inferiori sono scatole nere che implementano i livelli di astrazione in cima ai quali sono costruiti i livelli superiori. Se un livello inferiore (ad esempio, il livello B) può vedere i dettagli di uno strato superiore A, allora B non è più una scatola nera: i dettagli dell'implementazione dipendono da alcuni dettagli del proprio utente, ma l'idea di una scatola nera è che il suo il contenuto (la sua implementazione) è irrilevante per il suo utente!

    
______ azszpr198920 ___

Solo per divertimento.

Pensa a una piramide di cheerleaders. La riga inferiore supporta le righe sopra di loro.

Se la cheerleader di quella fila sta guardando in basso, sono stabili e rimarranno in equilibrio in modo che quelli sopra di lei non cadano.

Se alza lo sguardo per vedere come stanno facendo tutti quelli sopra di lei, perderà il suo equilibrio facendo cadere tutto lo stack.

Non proprio tecnico, ma era un'analogia che pensavo potesse aiutare.

    
______ azszpr200680 ___

Mentre la facilità di comprensione e in parte i componenti sostituibili sono certamente delle buone ragioni, una ragione altrettanto importante (e probabilmente la ragione per cui i livelli sono stati inventati in primo luogo) è dal punto di vista della manutenzione del software. La linea di fondo è che le dipendenze causano il potenziale per rompere le cose.

Ad esempio, supponiamo che A dipenda da B. Dato che nulla dipende da A, gli sviluppatori sono liberi di modificare A nei loro contenuti di cuori senza doversi preoccupare di poter rompere qualcosa di diverso da A. Tuttavia, se lo sviluppatore vuole cambiare B quindi qualsiasi modifica apportata a B potrebbe potenzialmente interrompere A. Questo era un problema frequente nei primi giorni del computer (si pensi allo sviluppo strutturato) in cui gli sviluppatori avrebbero risolto un errore in una parte del programma e avrebbe sollevato bug in parti apparentemente totalmente indipendenti del programma altrove. Tutto a causa delle dipendenze.

Per continuare con l'esempio, ora supponiamo che A dipenda da B AND B dipende da A. IOW, una dipendenza circolare. Ora, ogni volta che viene apportata una modifica ovunque potrebbe potenzialmente rompere l'altro modulo. Un cambiamento in B potrebbe ancora interrompere A, ma ora un cambiamento in A potrebbe anche spezzare B.

Quindi, nella tua domanda iniziale, se sei in una piccola squadra per un piccolo progetto, tutto questo è decisamente eccessivo perché puoi modificare i moduli liberamente secondo il tuo capriccio. Tuttavia, se si è su un progetto consistente, se tutti i moduli dipendono dagli altri, ogni volta che è necessaria una modifica potrebbe potenzialmente rompere gli altri moduli. In un progetto di grandi dimensioni, conoscere tutti gli impatti potrebbe essere difficile da determinare, quindi probabilmente perderai alcuni impatti.

La situazione peggiora in un grande progetto in cui vi sono molti sviluppatori (ad esempio, alcuni che lavorano solo sul livello A, alcuni sul livello B e alcuni sullo strato C). Poiché è probabile che ogni cambiamento debba essere rivisto / discusso con i membri degli altri livelli al fine di assicurarti che le tue modifiche non si interrompano o costringano a rielaborare ciò su cui stanno lavorando. Se le tue modifiche impongono cambiamenti agli altri, devi convincerli che dovrebbero apportare il cambiamento, perché non vorranno assumere più lavoro solo perché hai questo nuovo fantastico modo di fare le cose nel tuo modulo. IOW, un incubo burocratico.

Ma se si limitano le dipendenze ad A dipende da B, B dipende da C allora solo le persone di livello C devono coordinare le loro modifiche ad entrambi i team. Il livello B ha solo bisogno di coordinare le modifiche con il gruppo di livello A e il gruppo di livello A è libero di fare ciò che vuole perché il loro codice non influenza il livello B o C. Quindi idealmente, progetterai i tuoi livelli in modo che il livello C cambi molto poco, il livello B cambia un po 'e il livello A fa la maggior parte del cambiamento.

    
______ azszpr198834 ___

La ragione più basilare per cui gli strati inferiori non dovrebbero essere consapevoli degli strati superiori è che ci sono molti più tipi di strati superiori. Ad esempio, ci sono migliaia e migliaia di programmi diversi sul tuo sistema Linux, ma chiamano la stessa libreria C %code% . Quindi la dipendenza è da questi programmi a quella libreria.

Tieni presente che i "livelli inferiori" sono in realtà i livelli intermedi.

Pensa a un'applicazione che comunica attraverso il mondo esterno attraverso alcuni driver di dispositivo. Il sistema operativo è nel mezzo .

Il sistema operativo non dipende dai dettagli all'interno delle applicazioni, né dai driver di dispositivo. Esistono molti tipi di driver di dispositivo dello stesso tipo e condividono lo stesso framework di driver di periferica. A volte gli hacker del kernel devono mettere in atto alcuni casi speciali nel framework per il bene di un particolare hardware o dispositivo (esempio recente che ho trovato: codice specifico per PL2303 nel framework USB-seriale di Linux). Quando ciò accade, di solito inseriscono commenti su quanto questo fa schifo e dovrebbe essere rimosso. Anche se il sistema operativo chiama le funzioni nei driver, le chiamate passano attraverso i ganci che rendono i driver uguali, mentre quando i driver chiamano il sistema operativo, spesso usano direttamente funzioni specifiche per nome.

Quindi, in qualche modo, il sistema operativo è in realtà un livello inferiore dal punto di vista dell'applicazione e dal punto di vista dell'applicazione: una sorta di hub di comunicazione in cui le cose si connettono e i dati vengono scambiati per seguire i percorsi appropriati. Aiuta il design dell'hub di comunicazione ad esportare un servizio flessibile che può essere utilizzato da qualsiasi cosa, e non a spostare qualsiasi hacking specifico di dispositivo o applicazione nell'hub.

    
______ azszpr198954 ___

La separazione di preoccupazioni e approcci divide / conquista può essere un'altra spiegazione per queste domande. La separazione delle preoccupazioni offre la capacità di portabilità e in alcune architetture più complesse, offre alla piattaforma scalabilità e vantaggi prestazionali indipendenti.

In questo contesto, se si pensa a un'architettura a 5 livelli (client, presentazione, bussiness, integrazione e livello di risorse), il livello inferiore di architettura non dovrebbe essere consapevole della logica e della bussiness dei livelli superiori e viceversa. Intendo per livello inferiore come livelli di integrazione e risorse. Le interfacce di integrazione dei database fornite in integrazione e i database e i servizi web reali (fornitori di dati di terze parti) appartengono al livello delle risorse. Quindi supponiamo che cambierai il tuo database MySQL in un DB di documenti NoSQL come MangoDB in termini di scalabilità o altro.

In questo approccio, il livello di bussiness non interessa come il livello di integrazione fornisce la connessione / trasmissione dalla risorsa. Cerca solo gli oggetti di accesso ai dati forniti dal livello di integrazione. Questo potrebbe essere esteso a più scenari, ma fondamentalmente, la separazione delle preoccupazioni potrebbe essere la ragione numero uno per questo.

    
______ azszpr200672 ___

Espandendo la risposta di Kilian Foth, questa direzione di stratificazione corrisponde a una direzione in cui un essere umano esplora un sistema.

Immagina di essere un nuovo sviluppatore con il compito di correggere un bug nel sistema a livelli.

I bug di solito sono una discrepanza tra ciò che il cliente ha bisogno e ciò che ottiene. Mentre il cliente comunica con il sistema attraverso l'interfaccia utente e ottiene risultati attraverso l'interfaccia utente (UI letteralmente significa "interfaccia utente"), i bug sono riportati anche in termini di interfaccia utente. Quindi, come sviluppatore, non hai altra scelta che iniziare a guardare l'interfaccia utente, per capire cos'è successo.

Ecco perché è necessario disporre di connessioni layer top-down. Ora, perché non abbiamo connessioni in entrambe le direzioni?

Bene, hai tre scenari su come quel bug potrebbe mai accadere.

Potrebbe verificarsi nel codice UI stesso e quindi essere localizzato lì. È facile, devi solo trovare un posto e sistemarlo.

Potrebbe verificarsi in altre parti del sistema a seguito di chiamate effettuate dall'interfaccia utente. Che è moderatamente difficile, traccia un albero di chiamate, trova un posto dove si verifica l'errore e lo aggiusta.

E potrebbe verificarsi come risultato di una chiamata nel tuo codice UI. Il che è difficile, devi prendere la chiamata, trovare la sua fonte, quindi capire dove si verifica l'errore. Considerando che un punto di partenza è situato in profondità in un singolo ramo di un albero di chiamate, E devi trovare prima un albero di chiamata corretto, potrebbero esserci diverse chiamate nel codice dell'interfaccia utente, hai il tuo debug tagliato per te.

Per eliminare il più possibile il caso più difficile, le dipendenze circolari sono strongmente scoraggiate, i livelli si connettono per lo più in modalità top-down. Anche quando è necessaria una connessione dall'altra parte, di solito è limitata e chiaramente definita. Ad esempio, anche con i callback, che sono una sorta di connessione inversa, il codice richiamato in callback di solito fornisce questa callback in primo luogo, implementando una sorta di "opt-in" per le connessioni inverse e limitando il loro impatto sulla comprensione di un sistema.

La stratificazione è uno strumento e si rivolge principalmente agli sviluppatori che supportano un sistema esistente. Bene, anche le connessioni tra i livelli riflettono questo aspetto.

    
______ azszpr226467 ___

Un altro motivo per cui vorrei vedere qui esplicitamente è riusabilità del codice . Abbiamo già avuto l'esempio del supporto RS232 che viene sostituito, quindi facciamo un passo avanti ...

Immagina di sviluppare driver. È il tuo lavoro e scrivi un bel po '. Probabilmente i protocolli potrebbero iniziare a ripetersi ad un certo punto, come potrebbe essere il supporto fisico.

Quindi quello che inizierai a fare - a meno che tu non sia un grande fan di fare la stessa cosa più e più volte - è scrivere livelli riutilizzabili per queste cose.

Supponiamo di dover scrivere 5 driver per i dispositivi Modbus. Uno di questi usa Modbus TCP, due usano Modbus su RS485 e il resto passa su RS232. Non applicherete nuovamente Modbus 5 volte, perché state scrivendo 5 driver. Inoltre non hai intenzione di reimplementare Modbus 3 volte, perché hai 3 diversi livelli fisici sotto di te.

Quello che fai è scrivere un accesso multimediale TCP, un accesso multimediale RS485 e possibilmente un accesso multimediale RS232. È intelligente sapere che ci sarà uno strato modbus sopra, a questo punto? Probabilmente no. Il prossimo driver che verrà implementato potrebbe anche utilizzare Ethernet ma utilizzare HTTP-REST. Sarebbe un peccato se si dovesse reimplementare l'accesso ai media Ethernet per comunicare via HTTP.

Uno strato sopra, implementerai Modbus solo una volta. Quello strato Modbus ancora una volta, non conoscerà i driver, che sono uno strato in su. Questi driver, ovviamente, dovranno sapere che dovrebbero parlare di modbus, e dovrebbero sapere che usano Ethernet. Tuttavia, nonostante abbia implementato il modo in cui l'ho descritto, potresti non solo strappare un livello e sostituirlo. Ovviamente potresti - e questo per me è il più grande vantaggio di tutti, andare avanti e riutilizzare quello strato Ethernet esistente per qualcosa di assolutamente estraneo al progetto che originariamente ha causato la sua creazione.

Questo è qualcosa, probabilmente lo vediamo ogni giorno come sviluppatori e questo ci fa risparmiare un sacco di tempo. Esistono innumerevoli librerie per tutti i tipi di protocolli e altre cose. Questi esistono a causa di principi come la direzione delle dipendenze che segue la direzione del comando, che ci consente di creare livelli riutilizzabili di software.

    
___
risposta data 25.05.2013 - 00:13
fonte
3

Mentre la facilità di comprensione e in parte i componenti sostituibili sono certamente delle buone ragioni, una ragione altrettanto importante (e probabilmente la ragione per cui i livelli sono stati inventati in primo luogo) è dal punto di vista della manutenzione del software. La linea di fondo è che le dipendenze causano il potenziale per rompere le cose.

Ad esempio, supponiamo che A dipenda da B. Dato che nulla dipende da A, gli sviluppatori sono liberi di modificare A nei loro contenuti di cuori senza doversi preoccupare di poter rompere qualcosa di diverso da A. Tuttavia, se lo sviluppatore vuole cambiare B quindi qualsiasi modifica apportata a B potrebbe potenzialmente interrompere A. Questo era un problema frequente nei primi giorni del computer (si pensi allo sviluppo strutturato) in cui gli sviluppatori avrebbero risolto un errore in una parte del programma e avrebbe sollevato bug in parti apparentemente totalmente indipendenti del programma altrove. Tutto a causa delle dipendenze.

Per continuare con l'esempio, ora supponiamo che A dipenda da B AND B dipende da A. IOW, una dipendenza circolare. Ora, ogni volta che viene apportata una modifica ovunque potrebbe potenzialmente rompere l'altro modulo. Un cambiamento in B potrebbe ancora interrompere A, ma ora un cambiamento in A potrebbe anche spezzare B.

Quindi, nella tua domanda iniziale, se sei in una piccola squadra per un piccolo progetto, tutto questo è decisamente eccessivo perché puoi modificare i moduli liberamente secondo il tuo capriccio. Tuttavia, se si è su un progetto consistente, se tutti i moduli dipendono dagli altri, ogni volta che è necessaria una modifica potrebbe potenzialmente rompere gli altri moduli. In un progetto di grandi dimensioni, conoscere tutti gli impatti potrebbe essere difficile da determinare, quindi probabilmente perderai alcuni impatti.

La situazione peggiora in un grande progetto in cui vi sono molti sviluppatori (ad esempio, alcuni che lavorano solo sul livello A, alcuni sul livello B e alcuni sullo strato C). Poiché è probabile che ogni cambiamento debba essere rivisto / discusso con i membri degli altri livelli al fine di assicurarti che le tue modifiche non si interrompano o costringano a rielaborare ciò su cui stanno lavorando. Se le tue modifiche impongono cambiamenti agli altri, devi convincerli che dovrebbero apportare il cambiamento, perché non vorranno assumere più lavoro solo perché hai questo nuovo fantastico modo di fare le cose nel tuo modulo. IOW, un incubo burocratico.

Ma se si limitano le dipendenze ad A dipende da B, B dipende da C allora solo le persone di livello C devono coordinare le loro modifiche ad entrambi i team. Il livello B ha solo bisogno di coordinare le modifiche con il gruppo di livello A e il gruppo di livello A è libero di fare ciò che vuole perché il loro codice non influenza il livello B o C. Quindi idealmente, progetterai i tuoi livelli in modo che il livello C cambi molto poco, il livello B cambia un po 'e il livello A fa la maggior parte del cambiamento.

    
risposta data 06.06.2013 - 16:32
fonte
3

Vorrei aggiungere i miei due centesimi a ciò che Matt Fenwick e Kilian Foth hanno già spiegato.

Un principio dell'architettura del software è che i programmi complessi dovrebbero essere costruiti componendo blocchi più piccoli e autosufficienti (scatole nere): questo riduce al minimo le dipendenze riducendo così la complessità. Quindi, questa dipendenza unidirezionale è una buona idea perché rende più semplice la comprensione del software e la gestione della complessità è uno dei problemi più importanti nello sviluppo del software.

Quindi, in un'architettura a strati, i livelli inferiori sono scatole nere che implementano i livelli di astrazione in cima ai quali sono costruiti i livelli superiori. Se un livello inferiore (ad esempio, il livello B) può vedere i dettagli di uno strato superiore A, allora B non è più una scatola nera: i dettagli dell'implementazione dipendono da alcuni dettagli del proprio utente, ma l'idea di una scatola nera è che il suo il contenuto (la sua implementazione) è irrilevante per il suo utente!

    
risposta data 20.05.2013 - 20:55
fonte
3

Solo per divertimento.

Pensa a una piramide di cheerleaders. La riga inferiore supporta le righe sopra di loro.

Se la cheerleader di quella fila sta guardando in basso, sono stabili e rimarranno in equilibrio in modo che quelli sopra di lei non cadano.

Se alza lo sguardo per vedere come stanno facendo tutti quelli sopra di lei, perderà il suo equilibrio facendo cadere tutto lo stack.

Non proprio tecnico, ma era un'analogia che pensavo potesse aiutare.

    
risposta data 21.05.2013 - 19:08
fonte
1

La ragione più basilare per cui gli strati inferiori non dovrebbero essere consapevoli degli strati superiori è che ci sono molti più tipi di strati superiori. Ad esempio, ci sono migliaia e migliaia di programmi diversi sul tuo sistema Linux, ma chiamano la stessa libreria C malloc . Quindi la dipendenza è da questi programmi a quella libreria.

Tieni presente che i "livelli inferiori" sono in realtà i livelli intermedi.

Pensa a un'applicazione che comunica attraverso il mondo esterno attraverso alcuni driver di dispositivo. Il sistema operativo è nel mezzo .

Il sistema operativo non dipende dai dettagli all'interno delle applicazioni, né dai driver di dispositivo. Esistono molti tipi di driver di dispositivo dello stesso tipo e condividono lo stesso framework di driver di periferica. A volte gli hacker del kernel devono mettere in atto alcuni casi speciali nel framework per il bene di un particolare hardware o dispositivo (esempio recente che ho trovato: codice specifico per PL2303 nel framework USB-seriale di Linux). Quando ciò accade, di solito inseriscono commenti su quanto questo fa schifo e dovrebbe essere rimosso. Anche se il sistema operativo chiama le funzioni nei driver, le chiamate passano attraverso i ganci che rendono i driver uguali, mentre quando i driver chiamano il sistema operativo, spesso usano direttamente funzioni specifiche per nome.

Quindi, in qualche modo, il sistema operativo è in realtà un livello inferiore dal punto di vista dell'applicazione e dal punto di vista dell'applicazione: una sorta di hub di comunicazione in cui le cose si connettono e i dati vengono scambiati per seguire i percorsi appropriati. Aiuta il design dell'hub di comunicazione ad esportare un servizio flessibile che può essere utilizzato da qualsiasi cosa, e non a spostare qualsiasi hacking specifico di dispositivo o applicazione nell'hub.

    
risposta data 21.05.2013 - 05:40
fonte
1

La separazione di preoccupazioni e approcci divide / conquista può essere un'altra spiegazione per queste domande. La separazione delle preoccupazioni offre la capacità di portabilità e in alcune architetture più complesse, offre alla piattaforma scalabilità e vantaggi prestazionali indipendenti.

In questo contesto, se si pensa a un'architettura a 5 livelli (client, presentazione, bussiness, integrazione e livello di risorse), il livello inferiore di architettura non dovrebbe essere consapevole della logica e della bussiness dei livelli superiori e viceversa. Intendo per livello inferiore come livelli di integrazione e risorse. Le interfacce di integrazione dei database fornite in integrazione e i database e i servizi web reali (fornitori di dati di terze parti) appartengono al livello delle risorse. Quindi supponiamo che cambierai il tuo database MySQL in un DB di documenti NoSQL come MangoDB in termini di scalabilità o altro.

In questo approccio, il livello di bussiness non interessa come il livello di integrazione fornisce la connessione / trasmissione dalla risorsa. Cerca solo gli oggetti di accesso ai dati forniti dal livello di integrazione. Questo potrebbe essere esteso a più scenari, ma fondamentalmente, la separazione delle preoccupazioni potrebbe essere la ragione numero uno per questo.

    
risposta data 22.05.2013 - 00:52
fonte
1

Espandendo la risposta di Kilian Foth, questa direzione di stratificazione corrisponde a una direzione in cui un essere umano esplora un sistema.

Immagina di essere un nuovo sviluppatore con il compito di correggere un bug nel sistema a livelli.

I bug di solito sono una discrepanza tra ciò che il cliente ha bisogno e ciò che ottiene. Mentre il cliente comunica con il sistema attraverso l'interfaccia utente e ottiene risultati attraverso l'interfaccia utente (UI letteralmente significa "interfaccia utente"), i bug sono riportati anche in termini di interfaccia utente. Quindi, come sviluppatore, non hai altra scelta che iniziare a guardare l'interfaccia utente, per capire cos'è successo.

Ecco perché è necessario disporre di connessioni layer top-down. Ora, perché non abbiamo connessioni in entrambe le direzioni?

Bene, hai tre scenari su come quel bug potrebbe mai accadere.

Potrebbe verificarsi nel codice UI stesso e quindi essere localizzato lì. È facile, devi solo trovare un posto e sistemarlo.

Potrebbe verificarsi in altre parti del sistema a seguito di chiamate effettuate dall'interfaccia utente. Che è moderatamente difficile, traccia un albero di chiamate, trova un posto dove si verifica l'errore e lo aggiusta.

E potrebbe verificarsi come risultato di una chiamata nel tuo codice UI. Il che è difficile, devi prendere la chiamata, trovare la sua fonte, quindi capire dove si verifica l'errore. Considerando che un punto di partenza è situato in profondità in un singolo ramo di un albero di chiamate, E devi trovare prima un albero di chiamata corretto, potrebbero esserci diverse chiamate nel codice dell'interfaccia utente, hai il tuo debug tagliato per te.

Per eliminare il più possibile il caso più difficile, le dipendenze circolari sono strongmente scoraggiate, i livelli si connettono per lo più in modalità top-down. Anche quando è necessaria una connessione dall'altra parte, di solito è limitata e chiaramente definita. Ad esempio, anche con i callback, che sono una sorta di connessione inversa, il codice richiamato in callback di solito fornisce questa callback in primo luogo, implementando una sorta di "opt-in" per le connessioni inverse e limitando il loro impatto sulla comprensione di un sistema.

La stratificazione è uno strumento e si rivolge principalmente agli sviluppatori che supportano un sistema esistente. Bene, anche le connessioni tra i livelli riflettono questo aspetto.

    
risposta data 06.06.2013 - 15:23
fonte
-1

Un altro motivo per cui vorrei vedere qui esplicitamente è riusabilità del codice . Abbiamo già avuto l'esempio del supporto RS232 che viene sostituito, quindi facciamo un passo avanti ...

Immagina di sviluppare driver. È il tuo lavoro e scrivi un bel po '. Probabilmente i protocolli potrebbero iniziare a ripetersi ad un certo punto, come potrebbe essere il supporto fisico.

Quindi quello che inizierai a fare - a meno che tu non sia un grande fan di fare la stessa cosa più e più volte - è scrivere livelli riutilizzabili per queste cose.

Supponiamo di dover scrivere 5 driver per i dispositivi Modbus. Uno di questi usa Modbus TCP, due usano Modbus su RS485 e il resto passa su RS232. Non applicherete nuovamente Modbus 5 volte, perché state scrivendo 5 driver. Inoltre non hai intenzione di reimplementare Modbus 3 volte, perché hai 3 diversi livelli fisici sotto di te.

Quello che fai è scrivere un accesso multimediale TCP, un accesso multimediale RS485 e possibilmente un accesso multimediale RS232. È intelligente sapere che ci sarà uno strato modbus sopra, a questo punto? Probabilmente no. Il prossimo driver che verrà implementato potrebbe anche utilizzare Ethernet ma utilizzare HTTP-REST. Sarebbe un peccato se si dovesse reimplementare l'accesso ai media Ethernet per comunicare via HTTP.

Uno strato sopra, implementerai Modbus solo una volta. Quello strato Modbus ancora una volta, non conoscerà i driver, che sono uno strato in su. Questi driver, ovviamente, dovranno sapere che dovrebbero parlare di modbus, e dovrebbero sapere che usano Ethernet. Tuttavia, nonostante abbia implementato il modo in cui l'ho descritto, potresti non solo strappare un livello e sostituirlo. Ovviamente potresti - e questo per me è il più grande vantaggio di tutti, andare avanti e riutilizzare quello strato Ethernet esistente per qualcosa di assolutamente estraneo al progetto che originariamente ha causato la sua creazione.

Questo è qualcosa, probabilmente lo vediamo ogni giorno come sviluppatori e questo ci fa risparmiare un sacco di tempo. Esistono innumerevoli librerie per tutti i tipi di protocolli e altre cose. Questi esistono a causa di principi come la direzione delle dipendenze che segue la direzione del comando, che ci consente di creare livelli riutilizzabili di software.

    
risposta data 02.02.2014 - 21:20
fonte

Leggi altre domande sui tag