Diseño de software

DI, IoC y DIP: diferencias entre los tres conceptos, explicadas de forma completa

DI, IoC y DIP son conceptos de distintos niveles. DIP es el principio que determina de qué depender; IoC indica quién tiene el control; y DI es la técnica para proporcionar dependencias. Este artículo explica la diferencia con ejemplos y respuestas para entrevistas.

5 min de lectura
Imagen de portada de DI, IoC y DIP: diferencias entre los tres conceptos, explicadas de forma completa

Al estudiar desarrollo de software, probablemente todos nos hemos confundido alguna vez con estos tres términos: DI, IoC y DIP.

Cuando en cursos o artículos se dice algo como «un contenedor IoC inyecta dependencias mediante DI para cumplir DIP», es fácil empezar a confundir qué significa cada término.

Empecemos por la conclusión: los tres términos pertenecen a niveles distintos. DIP es el principio, es decir, por qué hacerlo. IoC es la dirección general que aplica ese principio, es decir, quién tiene el control. DI es la técnica concreta que implementa esa dirección, es decir, cómo proporcionar la dependencia.

Después de leer este artículo entenderás cómo se relacionan los tres conceptos y podrás responder sin dificultad si te preguntan por ellos en una entrevista. También he mantenido los ejemplos de código lo más breves posible.

Resumen de los tres términos en una línea

Es mucho más fácil si primero memorizas una línea para cada término.

  • DIP (Dependency Inversion Principle, principio de inversión de dependencias): principio de diseño que indica que debemos depender de abstracciones, como protocolos, y no de implementaciones concretas.
  • IoC (Inversion of Control, inversión de control): una estructura en la que el framework o contenedor controla el flujo del programa, en lugar de hacerlo tu código.
  • DI (Dependency Injection, inyección de dependencias): técnica que proporciona los objetos necesarios desde fuera en lugar de crearlos internamente.

La relación entre los tres es la siguiente.

DIP es el objetivo y el principio; IoC es el concepto amplio que permite alcanzarlo; y DI es uno de los métodos concretos para implementar IoC.

En resumen, el nivel de abstracción desciende en este orden: DIP > IoC > DI.

Diagrama jerárquico que parte de DIP, pasa por IoC y se divide en DI, callbacks y métodos plantilla
Los niveles que ocupan los tres términos, desde el principio hasta la técnica

DIP trata sobre de qué depender

DIP es la D de SOLID, el principio de inversión de dependencias entre los cinco principios fundamentales de diseño orientado a objetos.

La idea clave tiene dos partes: los módulos de alto nivel no deben depender de los de bajo nivel, y ambos deben depender de abstracciones.

Suena complicado, pero con código resulta mucho más sencillo.

Veamos un mal ejemplo en el que un servicio de pedidos está ligado directamente a un proveedor de pagos concreto, Kakao Pay.

// Mal ejemplo: dependencia directa de una clase concreta
class OrderService {
    private let pay = KakaoPay() // Para cambiar de proveedor de pagos hay que modificar esta parte
}

Para cambiar después a Naver Pay tendrías que modificar directamente el código de OrderService. Cada nuevo método de pago haría tambalearse al módulo de alto nivel.

DIP invierte esta situación. Se define una abstracción para el pago, un protocolo, y se depende únicamente de ella.

protocol PayGateway { func pay(amount: Int) }

class OrderService {
    private let pay: PayGateway // Depender de una abstracción, no de una clase concreta
    init(pay: PayGateway) { self.pay = pay }
}

Sea Kakao Pay o Naver Pay, basta con implementar PayGateway y OrderService no necesita cambios. Eso significa que la dirección de la dependencia se ha invertido.


IoC trata sobre quién tiene el control

IoC, o inversión de control, es un concepto algo más amplio.

Normalmente, el código que escribimos controla todo el flujo: crea objetos, llama métodos y decide el orden de ejecución.

IoC invierte ese control. En lugar de que nosotros llamemos al framework, el framework nos llama a nosotros.

También se conoce como el «principio de Hollywood»: «No nos llames, nosotros te llamaremos (Don’t call us, we’ll call you)».

Con un contenedor de DI como Swinject, el contenedor crea, gestiona y conecta los objetos en lugar de que tú los crees directamente. El control pasa de ti al contenedor.

Aquí hay un punto importante: IoC no se limita a DI.

  • Que un framework invoque un callback
  • El patrón Template Method
  • Que UIKit invoque por ti métodos del ciclo de vida como viewDidLoad

Todo esto son ejemplos de IoC. DI es solo un sub-concepto especializado en proporcionar dependencias.

Un teléfono antiguo sobre un escritorio con una tarjeta que dice «Don't call us, we'll call you»
La transferencia del control: eso es todo IoC

DI trata sobre cómo proporcionar dependencias

DI, o inyección de dependencias, es la técnica más representativa para implementar IoC.

En el ejemplo anterior de DIP, OrderService recibía PayGateway mediante su inicializador. Eso es DI: proporcionar desde fuera un objeto necesario en lugar de crearlo internamente.

Hay tres métodos principales de inyección.

Método de inyección Descripción Recomendación
Inyección por constructor Recibir la dependencia mediante init La más recomendada (inmutable y con dependencias obligatorias claras)
Inyección por método Recibirla como parámetro del método Para dependencias opcionales
Inyección por propiedad Asignarla a una propiedad después de crear el objeto No recomendada porque se convierte en opcional y var

En Swift se recomienda la inyección por inicializador. La dependencia queda fijada con let, lo que aporta seguridad, y es fácil proporcionar objetos falsos (Mocks) durante las pruebas.

La relación puede resumirse así.

Para respetar el principio DIP, transferimos el control siguiendo la dirección de IoC y usamos DI como medio concreto.

Esta frase resume de la forma más concisa la relación entre los tres términos.

Diagrama en una pizarra que conecta con flechas tres cajas: WHY, WHO y HOW
Separarlos en por qué, quién y cómo evita confusiones

Preguntas frecuentes

P. ¿DI e IoC no son lo mismo?

No. IoC es el concepto más amplio, mientras que DI es uno de varios métodos para implementar IoC. Toda DI es IoC, pero no todo IoC es DI.

P. ¿Seguir DIP implica usar DI automáticamente?

No necesariamente. DIP es el principio de «depender de abstracciones»; DI aparece cuando proporcionas desde fuera una implementación de esa abstracción. El principio y la técnica son conceptos distintos.

P. ¿Puedo usar DI sin una biblioteca como Swinject?

Sí. Pasar una dependencia directamente mediante el inicializador, como en el ejemplo de Swift anterior, también es DI. Swinject y Factory solo automatizan el proceso; DI no requiere una biblioteca.


En resumen: DIP es por qué, el principio; IoC es quién, el control; y DI es cómo, la técnica. Si recuerdas estas tres preguntas por separado, no volverás a confundirlas.

Cuando entiendes que están en niveles distintos, el código relacionado con DI se ve completamente diferente. Espero que este artículo te haya ayudado a organizarlo todo con claridad. ¡Ánimo!

Seguir leyendo