Principi d'inversió de dependències
En el disseny orientat a objectes, el principi d'inversió de dependències és una metodologia específica per a mòduls de programari dèbilment acoblats. Quan es segueix aquest principi, les relacions de dependència convencionals establertes des dels mòduls d'alt nivell que estableixen polítiques fins als mòduls de dependència de baix nivell s'inverteixen, fent així que els mòduls d'alt nivell siguin independents dels detalls d'implementació del mòdul de baix nivell. El principi estableix:[1]
A. Els mòduls d'alt nivell no haurien d'importar res dels mòduls de baix nivell. Tots dos haurien de dependre d'abstraccions (per exemple, interfícies).[2]
B. Les abstraccions no haurien de dependre de detalls. Els detalls (implementacions concretes) haurien de dependre d'abstraccions.
En dictar que both els objectes d'alt nivell com els de baix nivell han de dependre de la mateixa abstracció, aquest principi de disseny inverts la manera com algunes persones poden pensar sobre la programació orientada a objectes.[3]
La idea que hi ha darrere dels punts A i B d'aquest principi és que, a l'hora de dissenyar la interacció entre un mòdul d'alt nivell i un de baix nivell, la interacció s'ha de considerar com una interacció abstracta entre ells. Això té implicacions per al disseny tant del mòdul d'alt nivell com del de baix nivell: el mòdul de baix nivell s'ha de dissenyar tenint en compte la interacció i pot ser necessari canviar-ne la interfície d'ús.
En molts casos, pensar en la interacció en si mateixa com un concepte abstracte permet reduir l'acoblament entre els components sense introduir patrons de codificació addicionals i resulta en un esquema d'interacció més lleuger i menys dependent de la implementació. Quan aquest esquema d'interacció abstracte és genèric i clar, aquest principi de disseny condueix al patró d'inversió de dependències que es descriu a continuació.[4]
Patró de capes tradicional
[modifica]En l'arquitectura d'aplicacions convencional, els components de nivell inferior (per exemple, la capa d'utilitats) estan dissenyats per ser consumits per components de nivell superior (per exemple, la capa de polítiques), cosa que permet construir sistemes altament compostos. Aquí, els components de nivell superior depenen directament dels components de nivell inferior per dur a terme alguna tasca. Aquesta dependència dels components de nivell inferior limita les oportunitats de reutilització dels components de nivell superior.[5]

L'objectiu del patró d'inversió de dependències és evitar aquesta distribució altament acoblada amb la mediació d'una capa abstracta i augmentar la reusabilitat de les capes superiors i de polítiques.
Patró d'inversió de dependències
[modifica]Amb la introducció d'una capa abstracta, tant les capes d'alt nivell com les de nivell inferior redueixen les dependències tradicionals de dalt a baix. No obstant això, el concepte d'"inversió" no significa que les capes de nivell inferior depenguin directament de les capes de nivell superior. Més aviat, ambdues capes haurien de dependre d'abstraccions (interfícies) que exposin el comportament que necessiten les capes de nivell superior.

En una aplicació directa d'inversió de dependències, els abstractes són propietat de les capes superiors/de polítiques. Aquesta arquitectura agrupa els components superiors/de polítiques i les abstraccions que defineixen els serveis inferiors junts en el mateix paquet. Les capes de nivell inferior es creen mitjançant l'herència/implementació d'aquestes classes o interfícies abstractes.[6]
La inversió de les dependències i la propietat fomenta la reutilització de les capes superiors i de polítiques. Les capes superiors podrien utilitzar altres implementacions dels serveis inferiors. Quan els components de la capa de nivell inferior estan tancats o quan l'aplicació requereix la reutilització de serveis existents, és habitual que un adaptador faci de mediador entre els serveis i les abstraccions.
Generalització del patró d'inversió de dependències
[modifica]En molts projectes, el principi i el patró d'inversió de dependències es consideren com un únic concepte que s'hauria de generalitzar, és a dir, aplicar a totes les interfícies entre mòduls de programari. Hi ha almenys dues raons per a això:
- És més senzill veure un bon principi de pensament com un patró de codificació. Un cop codificada una classe abstracta o una interfície, el programador pot dir: "He fet la feina d'abstracció".
- Com que moltes eines de proves unitàries es basen en l'herència per aconseguir simulacions, l'ús d'interfícies genèriques entre classes (no només entre mòduls quan té sentit utilitzar la generalitat) es va convertir en la regla.
Si l'eina de simulació utilitzada només es basa en l'herència, pot ser necessari aplicar àmpliament el patró d'inversió de dependències. Això té importants inconvenients:
- La simple implementació d'una interfície sobre una classe no és suficient per reduir l'acoblament; només pensar en l'abstracció potencial de les interaccions pot conduir a un disseny menys acoblat.
- Implementar interfícies genèriques a tot arreu d'un projecte dificulta la comprensió i el manteniment. A cada pas, el lector es preguntarà quines són les altres implementacions d'aquesta interfície i la resposta generalment és: només simulacions.
- La generalització de la interfície requereix més codi de fontaneria, en particular fàbriques que generalment depenen d'un marc de treball d'injecció de dependències.
- La generalització de la interfície també restringeix l'ús del llenguatge de programació.
Restriccions de generalització
[modifica]La presència d'interfícies per aconseguir el patró d'inversió de dependències (DIP) té altres implicacions de disseny en un programa orientat a objectes:
- Totes les variables membre d'una classe han d'estar en interfícies o resums.
- Tots els paquets de classes concretes només s'han de connectar a través d'una interfície o paquets de classes abstractes.
- Cap classe hauria de derivar d'una classe concreta.
- Cap mètode hauria de sobreescriure un mètode implementat.[7]
- Tota instanciació de variables requereix la implementació d'un patró de creació com ara el mètode factory o el patró factory, o l'ús d'un marc de treball d'injecció de dependències.
Restriccions de simulació d'interfícies
[modifica]L'ús d'eines de simulació basades en herència també introdueix restriccions:
- Els membres estàtics visibles externament haurien de dependre sistemàticament de la injecció de dependències, cosa que els dificulta molt la implementació.
- Tots els mètodes comprovables haurien de convertir-se en una implementació d'interfície o una sobreescriptura d'una definició abstracta.
Implementacions
[modifica]Dues implementacions comunes de DIP utilitzen una arquitectura lògica similar però amb implicacions diferents.
Una implementació directa empaqueta les classes de polítiques amb classes d'abstractes de serveis en una biblioteca. En aquesta implementació, els components d'alt nivell i els components de baix nivell es distribueixen en paquets/biblioteques separades, on les interfícies que defineixen el comportament/serveis requerits pel component d'alt nivell són propietat de la biblioteca del component d'alt nivell i existeixen dins d'aquesta. La implementació de la interfície del component d'alt nivell pel component de baix nivell requereix que el paquet del component de baix nivell depengui del component d'alt nivell per a la compilació, invertint així la relació de dependència convencional.

Les figures 1 i 2 il·lustren codi amb la mateixa funcionalitat, però a la figura 2 s'ha utilitzat una interfície per invertir la dependència. La direcció de la dependència es pot triar per maximitzar la reutilització del codi de política i eliminar les dependències cícliques.
En aquesta versió de DIP, la dependència del component de la capa inferior de les interfícies/resums de les capes de nivell superior dificulta la reutilització dels components de la capa inferior. Aquesta implementació, en canvi, "inverteix" la dependència tradicional de dalt a baix a l'oposat, de baix a dalt.
Una solució més flexible extreu els components abstractes en un conjunt independent de paquets/biblioteques:

La separació de cada capa en el seu propi paquet fomenta la reutilització de qualsevol capa, proporcionant robustesa i mobilitat.[8]
Exemples
[modifica]Mòdul genealògic
[modifica]Un sistema genealògic pot representar les relacions entre persones com un gràfic de les relacions directes entre elles (pare-fill, pare-filla, mare-fill, mare-filla, marit-muller, esposa-marit, etc.). Això és molt eficient i extensible, ja que és fàcil afegir un exmarit o un tutor legal.
Però alguns mòduls de nivell superior poden requerir una manera més senzilla de navegar pel sistema: qualsevol persona pot tenir fills, pares, germans (inclosos germanastres o no), avis, cosins, etc.
Depenent de l'ús del mòdul genealògic, presentar les relacions comunes com a propietats directes diferents (amagar el gràfic) farà que l'acoblament entre un mòdul de nivell superior i el mòdul genealògic sigui molt més lleuger i permetrà canviar completament la representació interna de les relacions directes sense cap efecte sobre els mòduls que les utilitzen. També permet incrustar definicions exactes de germans o oncles al mòdul genealògic, fent complir així el principi de responsabilitat única.
Finalment, si el primer enfocament de graf generalitzat extensible sembla el més extensible, l'ús del mòdul genealògic pot demostrar que una implementació de relacions més especialitzada i senzilla és suficient per a l'aplicació o aplicacions i ajuda a crear un sistema més eficient.
En aquest exemple, l'abstracció de la interacció entre els mòduls condueix a una interfície simplificada del mòdul de nivell inferior i pot conduir a una implementació més senzilla del mateix.
Client de servidor de fitxers remot
[modifica]Un client de servidor de fitxers remot (FTP, emmagatzematge al núvol...) es pot modelar com un conjunt d'interfícies abstractes:
- Connexió/Desconnexió (pot ser necessària una capa de persistència de connexió)
- Interfície de creació/canvi de nom/supressió/llista de carpetes/etiquetes
- Interfície de creació/substitució/canvi de nom/supressió/lectura de fitxers
- Cerca de fitxers
- Resolució de substitució o eliminació concurrent
- Gestió de l'historial de fitxers...
Si els fitxers locals i remots ofereixen les mateixes interfícies abstractes, els mòduls d'alt nivell que implementen el patró d'inversió de dependències els poden utilitzar indiscriminadament. L'aplicació podrà desar els seus documents localment o remotament de manera transparent.
Cal tenir en compte el nivell de servei que requereixen els mòduls d'alt nivell.
Dissenyar un mòdul com un conjunt d'interfícies abstractes i adaptar-hi altres mòduls pot proporcionar una interfície comuna per a molts sistemes.
Model-vista-controlador
[modifica]
Els paquets UI i ApplicationLayer contenen principalment classes concretes. Els Controllers contenen abstractes/tipus d'interfície. La UI té una instància d'ICustomerHandler. Tots els paquets estan separats físicament. A l'ApplicationLayer hi ha una implementació concreta de CustomerHandler que la classe Page utilitzarà. Les instàncies de la interfície ICustomerHandler són creades dinàmicament per una Factory (possiblement al mateix paquet Controllers). Els tipus concrets Page i CustomerHandler depenen d'ICustomerHandler, no l'un de l'altre.
Com que la interfície d'usuari no fa referència a ApplicationLayer ni a cap altre paquet concret que implementi ICustomerHandler, la implementació concreta de CustomerHandler es pot substituir sense canviar la classe de la interfície d'usuari. A més, la classe Page implementa la interfície IPageViewer, que es pot passar com a argument als mètodes de l'ICustomerHandler, permetent que la implementació concreta de CustomerHandler es comuniqui amb la interfície d'usuari sense una dependència concreta. De nou, ambdues estan enllaçades per interfícies.
Història
[modifica]El principi d'inversió de dependències va ser postulat per Robert C. Martin i descrit en diverses publicacions, incloent-hi l'article Object Oriented Design Quality Metrics: an analysis of dependencies,[9] un article aparegut al C++ Report el juny de 1996 titulat The Dependency Inversion Principle i els llibres Agile Software Development, Principles, Patterns, and Practices[10] i Agile Principles, Patterns, and Practices in C#.
Referències
[modifica]- ↑ Martin, Robert C. Agile Software Development, Principles, Patterns, and Practices (en anglès). Prentice Hall, 2003, p. 127–131. ISBN 978-0135974445.
- ↑ Janssen, Thorben. «SOLID Design Principles Explained: Dependency Inversion» (en anglès americà), 31-07-2023. [Consulta: 20 agost 2025].
- ↑ Freeman, Eric. Hendrickson. Head First Design Patterns (paperback) (en anglès). 1. O'REILLY, 2004. ISBN 978-0-596-00712-6.
- ↑ «[https://objectmentor.com/resources/articles/dip.pdf The Dependency Inversion Principle]» (en anglès). [Consulta: 20 agost 2025].
- ↑ Martin, Robert C. Agile Software Development, Principles, Patterns, and Practices (en anglès). Prentice Hall, 2003, p. 127–131. ISBN 978-0135974445.
- ↑ Martin, Robert C. Agile Software Development, Principles, Patterns, and Practices (en anglès). Prentice Hall, 2003, p. 127–131. ISBN 978-0135974445.
- ↑ Martin, Robert C. Agile Software Development, Principles, Patterns, and Practices (en anglès). Prentice Hall, 2003, p. 127–131. ISBN 978-0135974445.
- ↑ Martin, Robert C. Agile Software Development, Principles, Patterns, and Practices. Prentice Hall, 2003, p. 127–131. ISBN 978-0135974445.
- ↑ Martin, Robert C. «Object Oriented Design Quality Metrics: An analysis of dependencies» (en anglès), 01-10-1994. [Consulta: 15 octubre 2016].
- ↑ Martin, Robert C. Agile Software Development, Principles, Patterns, and Practices (en anglès). Prentice Hall, 2003, p. 127–131. ISBN 978-0135974445.