Contenedor de Inyección de Dependencias en C++: biblioteca Fruit para la inyección de dependencias

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Contenedor de Inyección de Dependencias para C++: descripción general de la biblioteca Fruit



En el desarrollo moderno de C++, la gestión de dependencias entre clases se vuelve críticamente importante para crear aplicaciones flexibles, comprobables y mantenibles. El patrón Inyección de Dependencias (DI) permite sacar la creación de objetos fuera de las clases, y el contenedor DI automatiza este proceso. Una de las mejores bibliotecas para implementar la inyección de dependencias en C++ es Fruit — un contenedor ligero, rápido y type-safe que funciona en tiempo de compilación.



¿Qué es un contenedor de Inyección de Dependencias?

Un contenedor de Inyección de Dependencias es una herramienta que asume la responsabilidad de crear y vincular objetos (dependencias). En lugar de que la clase cree sus propias dependencias mediante new o fábricas, el contenedor las «inyecta» desde el exterior. Esto implementa el principio de Inversión de Control (IoC) y hace que el código esté débilmente acoplado.



¿Por qué Fruit?

La biblioteca Fruit (https://github.com/google/fruit) fue desarrollada en Google y se diferencia de otras alternativas (por ejemplo, Boost.DI) por las siguientes características:

  • Verificación en compilación: todas las vinculaciones se verifican en tiempo de compilación, lo que elimina errores en tiempo de ejecución relacionados con dependencias faltantes.
  • Sobrecarga mínima: Fruit no utiliza RTTI ni llamadas virtuales, generando código eficiente.
  • Simplicidad: solo requiere C++14 y archivos de cabecera — sin macros ni generación de código.
  • Soporte de componentes: se puede construir un grafo de dependencias a partir de módulos reutilizables.


Instalación

Fruit se distribuye como una biblioteca header-only (solo requiere fruit.h). Para instalarlo mediante vcpkg:

vcpkg install fruit

O clone el repositorio e incluya el archivo include/fruit/fruit.h.



Conceptos principales

En Fruit se utilizan tres elementos clave:

  • Inyector (Injector) — el contenedor que crea objetos.
  • Componente (Component) — un módulo que registra tipos y sus fábricas.
  • Anotaciones — para diferenciar distintas implementaciones de una misma interfaz.


Ejemplo de uso

Consideremos un ejemplo clásico: un servicio depende de un repositorio. Crearemos una interfaz y dos implementaciones:



// interfaces.hpp#include <fruit/fruit.h>#include <string>

class ILogger {public: virtual void log(const std::string& msg) = 0; virtual ~ILogger() = default;};

class IUserRepository {public: virtual std::string getUser(int id) = 0; virtual ~IUserRepository() = default;};


Implementaciones:

// implementations.cpp#include "interfaces.hpp"#include <iostream>

class ConsoleLogger : public ILogger {public: INJECT(ConsoleLogger()) = default; // Anotación Fruit para el constructor void log(const std::string& msg) override { std::cout << "[LOG] " << msg << std::endl; }};

class SqlRepository : public IUserRepository {public: INJECT(SqlRepository()) = default; std::string getUser(int id) override { return "User_" + std::to_string(id); }};


Servicio que utiliza ambas dependencias:

// user_service.cpp#include "interfaces.hpp"

class UserService {private: ILogger* logger; IUserRepository* repo;public: INJECT(UserService(ILogger* logger, IUserRepository* repo)) : logger(logger), repo(repo) {} void processUser(int id) { auto name = repo->getUser(id); logger->log("Processing user: " + name); }};


Ahora armaremos el componente

Recommendations