I contenitori IOC infrangono i principi OOP


51

Qual è lo scopo dei contenitori IOC? Le ragioni combinate di ciò possono essere semplificate come segue:

Quando si utilizzano i principi di sviluppo OOP / SOLID, l'iniezione di dipendenza diventa disordinata. O hai i punti di ingresso di livello superiore che gestiscono le dipendenze per più livelli sottostanti e passano le dipendenze in modo ricorsivo attraverso la costruzione, oppure hai un codice un po 'duplicato nei modelli di fabbrica / costruttore e interfacce che costruiscono dipendenze quando ne hai bisogno.

Non esiste un modo OOP / SOLID per eseguire questo E avere un codice super grazioso.

Se quella precedente affermazione è vera, come lo fanno i contenitori IOC? Per quanto ne so, non stanno usando una tecnica sconosciuta che non può essere fatta con DI manuale. Quindi l'unica spiegazione è che i contenitori IOC infrangono i principi OOP / SOLID usando accessi privati ​​di oggetti statici.

I container IOC infrangono i seguenti principi dietro le quinte? Questa è la vera domanda poiché ho una buona comprensione, ma ho la sensazione che qualcun altro abbia una migliore comprensione:

  1. Controllo dell'ambito . Lo scopo è la ragione di quasi tutte le decisioni che prendo sulla mia progettazione del codice. Blocco, locale, modulo, statico / globale. L'ambito dovrebbe essere molto esplicito, il più possibile a livello di blocco e il meno possibile in statico. Dovresti vedere dichiarazioni, istanze e finali del ciclo di vita. Mi fido della lingua e GC per gestire l'ambito purché sia ​​esplicito con esso. Nella mia ricerca ho scoperto che IOC Containers ha impostato la maggior parte o tutte le dipendenze come statiche e le controlla attraverso un'implementazione AOP dietro le quinte. Quindi nulla è trasparente.
  2. Incapsulamento . Qual è lo scopo dell'incapsulamento? Perché dovremmo mantenere così i membri privati? Per motivi pratici è così che gli implementatori della nostra API non possono interrompere l'implementazione cambiando lo stato (che dovrebbe essere gestito dalla classe del proprietario). Inoltre, per motivi di sicurezza, è impossibile che si verifichino iniezioni che sorpassano i nostri stati membri e aggirano la convalida e il controllo di classe. Quindi qualsiasi cosa (framework di derisione o framework IOC) che in qualche modo inietta codice prima della compilazione per consentire l'accesso esterno ai membri privati ​​è piuttosto enorme.
  3. Principio unico di responsabilità . In superficie, i contenitori IOC sembrano rendere le cose più pulite. Ma immagina come realizzeresti la stessa cosa senza i framework di supporto. Avresti costruttori con una dozzina di dipendenze passate. Ciò non significa coprirlo con IOC Containers, è una buona cosa! È un segno per ricodificare il codice e seguire SRP.
  4. Aperto / chiuso . Proprio come SRP non è solo di classe (applico SRP fino alle linee a responsabilità singola, per non parlare dei metodi). Open / Closed non è solo una teoria di alto livello per non alterare il codice di una classe. È una pratica di comprensione della configurazione del programma e controllo su ciò che viene modificato e ciò che viene esteso. I contenitori IOC possono cambiare il modo in cui le classi lavorano in tutto e in parte perché:

    • un. Il codice principale non sta determinando la commutazione delle dipendenze, lo è la configurazione del framework.

    • b. L'ambito potrebbe essere modificato in un momento che non è controllato dai membri chiamanti, ma è determinato esternamente da un framework statico.

Quindi la configurazione della classe non è davvero chiusa, si altera in base alla configurazione di uno strumento di terze parti.

Il motivo per cui questa è una domanda è perché non sono necessariamente un maestro di tutti i contenitori IOC. E mentre l'idea di un contenitore IOC è piacevole, sembrano essere solo una facciata che copre una scarsa implementazione. Ma per ottenere il controllo dell'ambito esterno, l'accesso dei membri privati ​​e il caricamento Lazy, devono continuare molte cose discutibili non banali. L'AOP è eccezionale, ma anche il modo in cui viene realizzato tramite i contenitori IOC è discutibile.

Posso fidarmi di C # e .NET GC per fare ciò che mi aspetto. Ma non posso riporre la stessa fiducia in uno strumento di terze parti che sta modificando il mio codice compilato per eseguire soluzioni alternative a cose che non sarei in grado di fare manualmente.

Ad esempio: Entity Framework e altri ORM creano oggetti fortemente tipizzati e li mappano su entità di database, oltre a fornire funzionalità di base a piastre di caldaia per eseguire CRUD. Chiunque potrebbe costruire il proprio ORM e continuare a seguire manualmente i principi OOP / SOLID. Ma quei quadri ci aiutano, quindi non dobbiamo reinventare la ruota ogni volta. Considerando che i contenitori IOC, a quanto pare, ci aiutano a aggirare intenzionalmente i principi OOP / SOLID e coprirlo.


13
+1 La correlazione tra IOCed Explicitness of codeè esattamente la cosa con cui ho problemi. Il DI manuale può facilmente diventare un lavoro ingrato, ma almeno mette il flusso esplicito del programma autonomo in un unico posto: "Ottieni ciò che vedi, vedi ciò che ottieni". Mentre i binding e le dichiarazioni dei contenitori IOC possono facilmente diventare un programma parallelo nascosto da solo.
Eugene Podskal,

6
Come è anche questa una domanda?
Stephen,

8
Sembra più un post sul blog che una domanda.
Bart van Ingen Schenau,

4
Ho una categoria interna di domande, "troppo polemiche", che spesso non è una ragione formale stretta, ma che io voto negativo e talvolta voto persino per chiudere "non una vera domanda". Ha due aspetti, però, e questa domanda soddisfa solo il primo. (1) La domanda afferma una tesi e chiede se è corretta, (2) l'interrogante risponde alle risposte in disaccordo, difendendo la posizione iniziale. La risposta di Matthew contraddice parte di ciò che afferma la domanda, quindi a meno che l'interrogante non scorra dappertutto, io la classifica personalmente come una domanda in buona fede, sebbene nella forma "Ho ragione, vero?"
Steve Jessop,

3
@BenAaronson Grazie Ben. Sto cercando di comunicare ciò che ho imparato attraverso la mia ricerca e ottenere risposte sulla natura dei contenitori IOC. Come risultato della mia ricerca ho trovato questi principi infranti. La migliore risposta alla mia domanda confermerebbe o negherebbe che i contenitori IOC possono in qualche modo fare cose che non possiamo fare manualmente senza infrangere i principi OOP / SOLID. A causa dei tuoi fantastici commenti, ho riformulato la mia domanda con altri esempi.
Suamere,

Risposte:


35

Esaminerò numericamente i tuoi punti, ma prima di tutto c'è qualcosa di cui dovresti stare molto attento: non confondere il modo in cui un consumatore utilizza una biblioteca con il modo in cui la biblioteca è implementata . Buoni esempi di questo sono Entity Framework (che tu stesso citi come una buona libreria) e MVC di ASP.NET. Entrambi fanno molto sotto il cofano, ad esempio con la riflessione, che non sarebbe assolutamente considerata un buon design se lo diffondessi attraverso il codice quotidiano.

Queste librerie sono assolutamente non "trasparente" come funzionano, o quello che stanno facendo dietro le quinte. Ma questo non è un danno, perché supportano buoni principi di programmazione nei loro consumatori . Quindi ogni volta che parli di una biblioteca come questa, ricorda che come consumatore di una biblioteca non è il tuo lavoro preoccuparti della sua implementazione o manutenzione. Dovresti solo preoccuparti di come aiuta o ostacola il codice che scrivi che utilizza la libreria. Non confondere questi concetti!

Quindi, per passare per punto:

  1. Immediatamente raggiungiamo ciò che posso solo supporre sia un esempio di quanto sopra. Dici che i contenitori IOC impostano la maggior parte delle dipendenze come statiche. Bene, forse alcuni dettagli di implementazione di come funzionano includono l'archiviazione statica (anche se dato che tendono ad avere un'istanza di un oggetto come Ninject IKernelcome memoria principale, ne dubito persino). Ma questa non è una tua preoccupazione!

    In realtà, un contenitore IoC è altrettanto esplicito con portata quanto l'iniezione di dipendenza dei poveri. (Continuerò a confrontare i contenitori IoC con i DI del povero perché confrontarli con nessun DI sarebbe ingiusto e confuso. E, per essere chiari, non sto usando "DI del povero" come peggiorativo.)

    Nel DI del povero, avresti un'istanza manuale di una dipendenza, quindi la inietteresti nella classe che ne ha bisogno. Nel punto in cui costruisci la dipendenza, sceglieresti cosa fare con essa: archiviarla in una variabile con ambito locale, una variabile di classe, una variabile statica, non archiviarla affatto, qualunque cosa. È possibile passare la stessa istanza in molte classi oppure crearne una nuova per ogni classe. Qualunque cosa. Il punto è, per vedere quale sta accadendo, si guarda al punto - probabilmente vicino alla radice dell'applicazione - dove viene creata la dipendenza.

    E che dire di un contenitore IoC? Bene, fai esattamente lo stesso! Ancora una volta, passando per Ninject terminologia, si guarda dove è configurato il legame, e scoprire se si dice qualcosa del tipo InTransientScope, InSingletonScopeo qualsiasi altra cosa. Semmai questo è potenzialmente più chiaro, perché hai il codice proprio lì che dichiara il suo ambito, piuttosto che dover guardare attraverso un metodo per tenere traccia di ciò che accade a un oggetto (un oggetto può essere impostato su un blocco, ma viene usato più volte in quello blocco o solo una volta, per esempio). Quindi forse ti viene respinto dall'idea di dover utilizzare una funzionalità sul contenitore IoC piuttosto che una funzionalità in linguaggio grezzo per dettare l'ambito, ma finché ti fidi della tua libreria IoC - cosa che dovresti! - non ci sono svantaggi reali .

  2. Ancora non so davvero di cosa stai parlando qui. I contenitori IoC considerano le proprietà private come parte della loro implementazione interna? Non so perché lo farebbero, ma ancora una volta, se lo fanno, non è una tua preoccupazione come viene implementata una libreria che stai usando.

    O, forse, espongono una capacità come l'iniezione in setter privati? Onestamente non l'ho mai visto, e sono dubbioso sul fatto che questa sia davvero una caratteristica comune. Ma anche se è lì, è un semplice caso di uno strumento che può essere utilizzato in modo improprio. Ricorda, anche senza un contenitore IoC, è solo poche righe di codice Reflection per accedere e modificare una proprietà privata. È qualcosa che non dovresti quasi mai fare, ma ciò non significa che .NET sia dannoso per esporre la funzionalità. Se qualcuno utilizza in modo così evidente e sfrenato uno strumento, è colpa della persona, non dello strumento.

  3. Il punto finale qui è simile a 2. La stragrande maggioranza delle volte, i contenitori IoC usano il costruttore! L'iniezione settata viene offerta per circostanze molto specifiche in cui, per motivi particolari, non è possibile utilizzare l'iniezione del costruttore. Chiunque usi costantemente l'iniezione di setter per nascondere quante dipendenze vengono passate, sta massacrando enormemente lo strumento. NON è colpa dello strumento, è loro.

    Ora, se questo fosse un errore davvero facile da fare innocentemente, e uno che i contenitori IoC incoraggiano, allora va bene, forse avresti ragione. Sarebbe come rendere pubblico ogni membro della mia classe, quindi dare la colpa ad altre persone quando modificano cose che non dovrebbero, giusto? Chiunque utilizzi l'iniezione di setter per nascondere le violazioni di SRP o ignora volontariamente o ignora completamente i principi di progettazione di base. È irragionevole dare la colpa a questo nel contenitore IoC.

    Questo è particolarmente vero perché è anche qualcosa che puoi fare altrettanto facilmente con il DI del povero:

    var myObject 
       = new MyTerriblyLargeObject { DependencyA = new Thing(), DependencyB = new Widget(), Dependency C = new Repository(), ... };
    

    Quindi, davvero, questa preoccupazione sembra completamente ortogonale all'utilizzo o meno di un contenitore IoC.

  4. Cambiare il modo in cui le lezioni lavorano insieme non è una violazione di OCP. Se lo fosse, allora tutte le inversioni di dipendenza incoraggerebbero una violazione dell'OCP. Se così fosse, non sarebbero entrambi nello stesso acronimo SOLID!

    Inoltre, né i punti a) né b) si avvicinano ad avere a che fare con OCP. Non so nemmeno come rispondere a queste domande rispetto all'OCP.

    L'unica cosa che posso immaginare è che pensi che OCP abbia qualcosa a che fare con il comportamento che non viene modificato in fase di esecuzione o su dove nel codice è controllato il ciclo di vita delle dipendenze. Non è. OCP consiste nel non dover modificare il codice esistente quando i requisiti vengono aggiunti o cambiati. Si tratta solo di scrivere codice, non di come incollare insieme il codice che hai già scritto (anche se, naturalmente, l'accoppiamento lento è una parte importante del raggiungimento dell'OCP).

E un'ultima cosa che dici:

Ma non posso riporre la stessa fiducia in uno strumento di terze parti che sta modificando il mio codice compilato per eseguire soluzioni alternative a cose che non sarei in grado di fare manualmente.

Sì, puoi . Non c'è assolutamente alcun motivo per cui tu pensi che questi strumenti, fatti valere da un gran numero di progetti, siano più corretti o inclini a comportamenti imprevisti rispetto a qualsiasi altra libreria di terze parti.

appendice

Ho appena notato che anche i tuoi paragrafi introduttivi potrebbero usare alcuni indirizzi. Dici sardonicamente che i contenitori IoC "non stanno usando una tecnica segreta di cui non abbiamo mai sentito parlare" per evitare codice disordinato e soggetto a duplicazione per creare un grafico delle dipendenze. E hai ragione, quello che stanno facendo è in realtà affrontare queste cose con le stesse tecniche di base come facciamo sempre i programmatori.

Lascia che ti parli attraverso uno scenario ipotetico. Come programmatore, hai messo insieme una grande applicazione, e nel punto di ingresso, dove stai costruendo il tuo grafico a oggetti, noti che hai un codice piuttosto disordinato. Ci sono alcune classi che vengono usate più e più volte e ogni volta che ne costruisci una devi ricostruire l'intera catena di dipendenze sotto di esse. Inoltre, scopri di non avere alcun modo espressivo per dichiarare o controllare il ciclo di vita delle dipendenze, ad eccezione del codice personalizzato per ognuna. Il tuo codice non è strutturato e pieno di ripetizioni. Questa è la confusione di cui parli nel tuo paragrafo introduttivo.

Quindi, per prima cosa, inizi a riformattare un po '- dove un po' di codice ripetuto è abbastanza strutturato da estrarlo in metodi di supporto, e così via. Ma poi inizi a pensare: è un problema che potrei forse affrontare in senso generale , uno che non è specifico di questo particolare progetto ma potrebbe aiutarti in tutti i tuoi progetti futuri?

Quindi ti siedi, ci pensi e decidi che dovrebbe esserci una classe in grado di risolvere le dipendenze. E fai uno schizzo dei metodi pubblici di cui avrebbe bisogno:

void Bind(Type interfaceType, Type concreteType, bool singleton);
T Resolve<T>();

Binddice "dove vedi un argomento di tipo costruttivo interfaceType, passa un'istanza di concreteType". Il singletonparametro aggiuntivo indica se utilizzare la stessa istanza di concreteTypeogni volta o crearne sempre una nuova.

Resolveproverà semplicemente a costruire Tcon qualsiasi costruttore che possa trovare i cui argomenti sono tutti i tipi che sono stati precedentemente associati. Può anche chiamarsi in modo ricorsivo per risolvere le dipendenze fino in fondo. Se non è in grado di risolvere un'istanza perché non tutto è stato associato, genera un'eccezione.

Puoi provare a implementarlo tu stesso e scoprirai che hai bisogno di un po 'di riflessione e di un po' di cache per gli attacchi dove singletonè vero, ma certamente niente di drastico o terrificante. E una volta terminato , voilà , hai il nucleo del tuo contenitore IoC! È davvero così spaventoso? L'unica vera differenza tra questo e Ninject o StructureMap o Castle Windsor o qualunque cosa tu preferisca è che quelli hanno molte più funzionalità per coprire i (molti!) Casi d'uso in cui questa versione di base non sarebbe sufficiente. Ma al centro, quello che hai lì è l'essenza di un contenitore IoC.


Grazie Ben. Onestamente ho lavorato con IOC Containers per circa un anno, e sebbene non lo faccia, tutorial e colleghi intorno a me si concentrano davvero sull'iniezione di proprietà (incluso il privato). È pazzo e fa emergere tutti i punti che faccio. Ma anche se altri si trovano in questa situazione, penso che la cosa migliore che possano fare sia imparare di più su come i contenitori IOC dovrebbero funzionare per seguire i principi e sottolineare questi principi nella revisione del codice. Tuttavia ... (continua)
Suamere,

La mia più grande preoccupazione non è necessariamente elencata nei Principi OOP / SOLID, e questo è esplicativo di portata. Continuo a lanciare l'ambito Block, Local, Module e Global level capendo la finestra per una parola chiave. L'idea di usare i blocchi e le dichiarazioni che determinano quando qualcosa cade fuori campo o è disposto è enorme per me, e penso che sia stata la parte più spaventosa di IOC Containers per me, per sostituire quel pezzo di sviluppo molto basilare con una parola chiave di estensione .
Suamere,

Quindi ho contrassegnato la tua risposta come risposta perché si rivolge davvero al cuore di questo. E il modo in cui lo leggo è che devi solo fidarti di ciò che fanno i contenitori IOC sotto le coperte perché lo fanno tutti gli altri. E per quanto riguarda le aperture per un possibile uso improprio e il modo elegante in cui lo copre, questo è per un negozio e le recensioni di codice di cui preoccuparsi e dovrebbero fare la loro dovuta diligenza per seguire buoni principi di sviluppo.
Suamere,

2
Per quanto riguarda 4, Dependency Injection non è nell'acronimo SOLID . 'D' = 'Principio di inversione di dipendenza', che è un concetto totalmente indipendente.
astreltsov,

@astr Buon punto. Ho sostituito "iniezione" con "inversione" poiché penso che sia quello che intendevo scrivere in primo luogo. È ancora un po 'una semplificazione eccessiva poiché l'inversione di dipendenza non implica necessariamente molteplici implementazioni concrete, ma penso che il significato sia abbastanza chiaro. Sarebbe pericoloso dipendere da un'astrazione polimorfica se cambiare le implementazioni fosse una violazione dell'OCP.
Ben Aaronson,

27

I contenitori IOC non infrangono molti di questi principi?

In teoria? No. I contenitori IoC sono solo uno strumento. Puoi avere oggetti che hanno una singola responsabilità, interfacce ben separate, non è possibile modificare il loro comportamento una volta scritto, ecc.

In pratica? Assolutamente.

Voglio dire, non ho sentito alcuni programmatori affermare che usano contenitori IoC specificamente per consentire all'utente di utilizzare una configurazione per modificare il comportamento del sistema, violando il principio aperto / chiuso. In passato venivano fatte battute sulla codifica in XML perché il 90% del loro lavoro correggeva la configurazione di Spring. E sicuramente hai ragione nel nascondere le dipendenze (come è vero, non tutti i contenitori IoC) è un modo semplice e veloce per creare un codice altamente accoppiato e molto fragile.

Diamo un'occhiata in un modo diverso. Consideriamo una classe molto semplice che ha una singola dipendenza di base. Dipende da Actionun delegato o un funzione che non accetta parametri e non restituisce void. Qual è la responsabilità di questa classe? Non puoi dirlo. Potrei nominare quella dipendenza come qualcosa di chiaro CleanUpActionche ti fa pensare che fa quello che vuoi che faccia. Non appena l'IoC entra in gioco diventa qualcosa . Chiunque può accedere alla configurazione e modificare il software per eseguire operazioni arbitrarie.

"In che modo è diverso dal passare Actional costruttore?" tu chiedi. È diverso perché non è possibile modificare ciò che viene passato nel costruttore una volta distribuito il software (o in fase di esecuzione!). È diverso perché i test unitari (o test unitari dei consumatori) hanno coperto gli scenari in cui il delegato è passato al costruttore.

"Ma questo è un esempio falso! Le mie cose non permettono di cambiare comportamento!" esclami. Mi dispiace dirtelo, ma lo fa. A meno che l'interfaccia che stai iniettando non siano solo dati. E la tua interfaccia non è solo dati, perché se fossero solo dati costruiresti un oggetto semplice e lo utilizzeresti. Non appena hai un metodo virtuale nel tuo punto di iniezione, il contratto della tua classe usando IoC è "Posso fare qualsiasi cosa ".

"In che modo è diverso dall'uso degli script per guidare le cose? Va benissimo." alcuni di voi chiedono. Non è diverso. Dal punto di vista della progettazione del programma, non c'è differenza. Stai chiamando dal tuo programma per eseguire codice arbitrario. E sicuramente, questo è stato fatto nei giochi per secoli. È fatto in tonnellate di strumenti di amministrazione dei sistemi. È OOP? Non proprio.

Non ci sono scuse per la loro attuale ubiquità. I contenitori IoC hanno un solido scopo: incollare le tue dipendenze quando ne hai così tante combinazioni, o si spostano così rapidamente che non è pratico renderle esplicite. Tuttavia, pochissime aziende si adattano effettivamente a questo scenario.


3
Giusto. E molti negozi affermeranno che il loro prodotto è così complesso che rientrano in quella categoria, quando in realtà non lo fanno. Come è la verità con molti scenari.
Suamere,

12
Ma l'IoC è esattamente l'opposto di quello che hai detto. Impiega Open / Closeness del programma. Se è possibile modificare il comportamento dell'intero programma senza dover modificare il codice stesso.
Euforico,

6
@Euforico: aperto per estensione, chiuso per modifica. Se puoi cambiare il comportamento dell'intero programma, non è chiuso per modifica, vero? La configurazione IoC è ancora il tuo software, è ancora il codice utilizzato dalle tue classi per fare il lavoro, anche se è in XML piuttosto che Java o C # o altro.
Telastyn,

4
@Telastyn "Chiuso per modifica" significa che non dovrebbe essere necessario cambiare (modificare) il codice, per cambiare il comportamento di un intero. Con qualsiasi configurazione esterna, puoi semplicemente indicarlo a una nuova dll e avere il comportamento di un intero cambiamento.
Euforico,

12
@Telastyn Non è quello che dice il principio di apertura / chiusura. Altrimenti un metodo che accetta parametri violerebbe OCP perché potrei "modificare" il suo comportamento passandogli parametri diversi. Allo stesso modo sotto la tua versione di OCP, sarebbe in conflitto diretto con DI, il che renderebbe piuttosto strano che entrambi compaiano nell'acronimo SOLID.
Ben Aaronson,

14

L'uso di un contenitore IoC viola necessariamente i principi OOP / SOLID? No .

Potete usare i contenitori IoC in modo tale da violare i principi OOP / SOLID? .

I contenitori IoC sono solo framework per la gestione delle dipendenze nell'applicazione.

Hai dichiarato iniezioni pigre, setter di proprietà e alcune altre caratteristiche che non ho ancora sentito la necessità di utilizzare, che concordo con te possono darti un design non ottimale. Tuttavia, gli utilizzi di queste funzionalità specifiche sono dovuti a limitazioni nella tecnologia in cui si implementano i contenitori IoC.

Ad esempio, ASP.NET WebForms, è necessario utilizzare l'iniezione di proprietà in quanto non è possibile creare un'istanza a Page. Questo è comprensibile perché WebForms non è mai stato progettato pensando ai rigidi paradigmi OOP.

ASP.NET MVC, d'altra parte, è del tutto possibile utilizzare un contenitore IoC usando l'iniezione del costruttore molto OOP / SOLID.

In questo scenario (usando l'iniezione del costruttore), credo che promuova i principi SOLIDI per i seguenti motivi:

  • Principio della singola responsabilità - Avere molte classi più piccole consente a queste classi di essere molto specifiche e avere una ragione per esistere. L'uso di un contenitore IoC rende l'organizzazione molto più semplice.

  • Aperto / Chiuso: è qui che brilla davvero l'uso di un contenitore IoC, è possibile estendere la funzionalità di una classe iniettando dipendenze diverse. Avere dipendenze che si iniettano consente di modificare il comportamento di quella classe. Un buon esempio sta cambiando la classe che stai iniettando scrivendo un decoratore per dire aggiungi cache o registrazione. È possibile modificare completamente le implementazioni delle proprie dipendenze se scritte con un livello di astrazione adeguato.

  • Principio di sostituzione di Liskov - Non molto rilevante

  • Principio di segregazione dell'interfaccia: se lo si desidera, è possibile assegnare più interfacce a un unico momento concreto, qualsiasi contenitore IoC degno di un granello di sale lo farà facilmente.

  • Inversione delle dipendenze: i contenitori IoC consentono di eseguire facilmente l'inversione delle dipendenze.

Definisci un sacco di dipendenze, dichiari relazioni e ambiti e bing bang boom: hai magicamente qualche addon che altera il tuo codice o IL a un certo punto e fa funzionare le cose. Questo non è esplicito o trasparente.

È sia esplicito che trasparente, devi dire al tuo contenitore IoC come collegare le dipendenze e come gestire la durata degli oggetti. Ciò può avvenire per convenzione, per file di configurazione o per codice scritto nelle principali applicazioni del sistema.

Concordo sul fatto che quando scrivo o leggo il codice utilizzando i contenitori IOC, si elimina un po 'di codice inelegante.

Questa è la ragione per cui OOP / SOLID rende i sistemi gestibili. L'uso di un contenitore IoC è in linea con tale obiettivo.

Lo scrittore di quella classe dice: "Se non usassimo un contenitore IOC, il costruttore avrebbe una dozzina di parametri !!! È orribile!"

Una classe con 12 dipendenze è probabilmente una violazione di SRP, indipendentemente dal contesto in cui la stai utilizzando.


2
Risposta eccellente. Inoltre, penso che tutti i principali contenitori IOC supportino anche AOP, il che può aiutare molto con OCP. Per preoccupazioni trasversali, AOP è di solito un percorso più OCP-friendly di un decoratore.
Ben Aaronson,

2
@BenAaronson Considerando che AOP inietta il codice in una classe, non lo definirei più OCP-friendly. Stai forzatamente aprendo e modificando una classe in fase di compilazione / runtime.
Doval,

1
@Doval Non vedo come stia iniettando codice in una classe. Nota, sto parlando dello stile Castle DynamicProxy, in cui hai un intercettore, piuttosto che dello stile di tessitura IL. Ma un intercettore è essenzialmente solo un decoratore che usa un po 'di riflessione in modo che non debba accoppiarsi a una particolare interfaccia.
Ben Aaronson,

"Inversione delle dipendenze - I contenitori IoC ti consentono di fare facilmente l'inversione delle dipendenze" - non lo fanno, stanno semplicemente sfruttando qualcosa che è vantaggioso che tu abbia o meno l'IoC. Lo stesso con altri principi.
Den

Non capisco davvero la logica della spiegazione che qualcosa non va male in base al fatto, che è una struttura. Anche i ServiceLocator sono realizzati come framework e sono considerati cattivi :).
ipavlu,

5

I contenitori IOC non si rompono entrambi e consentono ai programmatori di rompere molti principi OOP / SOLID?

La staticparola chiave in C # consente agli sviluppatori di violare molti dei principi OOP / SOLID ma è comunque uno strumento valido nelle giuste circostanze.

I contenitori IoC consentono un codice ben strutturato e riducono il carico cognitivo per gli sviluppatori. Un contenitore IoC può essere utilizzato per semplificare notevolmente i seguenti principi SOLID. Può essere usato in modo improprio, certo, ma se escludessimo l'uso di qualsiasi strumento che può essere utilizzato in modo improprio, dovremmo rinunciare del tutto alla programmazione.

Quindi, sebbene questo rant sollevi alcuni punti validi sul codice scritto male, non è esattamente una domanda e non è esattamente utile.


È mia comprensione dei contenitori IOC che, al fine di gestire l'ambito, la pigrizia e l'accessibilità, altera il codice compilato per fare cose che rompono OOP / SOLID, ma nasconde tale implementazione in un quadro piacevole. Ma sì, apre anche altre porte per un uso errato. La migliore risposta alla mia domanda sarebbe un chiarimento su come sono implementati i contenitori IOC che aggiungono o annullano la mia ricerca.
Suamere,

2
@Suamere - Penso che potresti usare una definizione troppo ampia di CIO. Quello che uso non modifica il codice compilato. Esistono molti altri strumenti non IOC che modificano anche il codice compilato. Forse il tuo titolo dovrebbe essere più simile a: "La modifica del codice compilato si interrompe ...".
Cerad,

@Suamere Non sono così esperto di IoC, ma non ho mai sentito parlare di IoC che modifica il codice compilato. Potresti fornire informazioni su quali piattaforme / lingue / framework stai parlando.
Euforico,

1
"Un contenitore IoC può essere utilizzato per semplificare molto il rispetto dei principi SOLID." - Puoi approfondire per favore? Spiegare in che modo sarebbe utile con ciascun principio sarebbe utile. E naturalmente sfruttare qualcosa non equivale ad aiutare a succedere qualcosa (ad es. DI).
Den

1
@Euforico Non conosco i framework .net di cui parlava suamere, ma Spring fa certamente delle modifiche al codice nascosto. L'esempio più ovvio è il suo supporto per l'iniezione basata sul metodo (per cui sostituisce un metodo astratto nella tua classe per restituire una dipendenza che gestisce). Fa inoltre ampio uso di proxy generati automaticamente al fine di racchiudere il codice per fornire comportamenti aggiuntivi (ad esempio per la gestione automatica delle transazioni). Gran parte di questo non è in realtà correlato al suo ruolo di framework DI, tuttavia.
Jules,

3

Vedo più errori e incomprensioni nella tua domanda. Il primo è che manca la distinzione tra iniezione di proprietà / campo, iniezione del costruttore e localizzatore di servizio. Ci sono state molte discussioni (ad es. Guerre di fiamma) su quale sia il modo giusto di fare le cose. Nella tua domanda, sembri assumere solo l'iniezione di proprietà e ignorare l'iniezione del costruttore.

La seconda cosa è che supponi che i contenitori IoC influenzino in qualche modo la progettazione del tuo codice. Non lo fanno, o almeno, non dovrebbe essere necessario cambiare il design del tuo codice se usi l'IoC. I tuoi problemi con le classi con molti costruttori sono casi di IoC che nasconde problemi reali con il tuo codice (molto probabilmente una classe con interruzioni SRP) e dipendenze nascoste sono uno dei motivi per cui le persone scoraggiano l'uso dell'iniezione di proprietà e del localizzatore di servizi.

Non capisco da dove provenga il problema con il principio aperto-chiuso, quindi non voglio commentarlo. Ma ti manca una parte di SOLID, che ha una grande influenza su questo: principio di inversione di dipendenza. Se segui DIP, scoprirai che l'inversione di una dipendenza introduce un'astrazione, la cui implementazione deve essere risolta quando viene creata un'istanza di classe. Con questo approccio, ti ritroverai con molte classi, che richiedono che vengano realizzate e fornite interfacce multiple alla costruzione per funzionare correttamente. Se poi dovessi fornire quelle dipendenze a mano, dovresti fare un sacco di lavoro aggiuntivo. I contenitori IoC semplificano l'esecuzione di questo lavoro e consentono persino di modificarlo senza dover ricompilare il programma.

Quindi, la risposta alla tua domanda è no. I contenitori IoC non infrangono i principi di progettazione OOP. Al contrario, risolvono i problemi causati dal seguire i principi SOLID (in particolare il principio di dipendenza-inversione).


Ho provato ad applicare IoC (Autofac) a un progetto non banale di recente. Mi sono reso conto che dovevo svolgere un lavoro simile con costrutti non linguistici (new () vs API): specificando cosa dovrebbe essere risolto in interfacce e cosa dovrebbe essere risolto in classi concrete, specificando cosa dovrebbe essere un'istanza e cosa dovrebbe essere un singleton, assicurarsi che l'iniezione di proprietà funzioni correttamente per alleviare una dipendenza circolare. Deciso di abbandonare. Funziona bene con i controller in ASP MVC però.
Den

@Den Il tuo progetto non è stato ovviamente progettato pensando all'inversione delle dipendenze. E da nessuna parte ho detto che usare l'IoC è banale.
Euforico,

In realtà, questa è la cosa: sto usando la Dependency Inversion dall'inizio. L'IoC che influenza il design oltre a ciò è la mia più grande preoccupazione. Ad esempio, perché dovrei usare le interfacce ovunque solo per semplificare l'IoC? Vedi anche il commento principale sulla domanda.
Den

@Non hai tecnicamente "bisogno" di usare le interfacce. Le interfacce aiutano a deridere i test unitari. Puoi anche contrassegnare le cose che devi prendere in giro virtuale.
Fred,
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.