c++ - 如何正确使用IoC容器?

标签 c++ inversion-of-control ioc-container kangaru

我对 IoC 想法非常陌生,我正在尝试跳过服务定位器模式。 我选择了 Kangaru 实现。假设我想在应用程序的不同位置使用音频和记录器服务。我现在拥有的:

    #include <kangaru/kangaru.hpp>
    #include <iostream>
    using namespace std;

    struct IAudio {
        virtual void playSound(int id) = 0;
    };

    struct Audio : IAudio {
        void playSound(int id) { cout << "Playing " << id << '\n'; }
    };

    struct IAudioService : kgr::abstract_service<IAudio> {};
    struct AudioService : kgr::single_service<Audio>, kgr::overrides<IAudioService> {};
    
    struct ILogger {
        virtual void log(const std::string& message) = 0;
    }; 

    struct Logger : ILogger {
        void log(const std::string& message) { cout << "Log: " << message << '\n'; }
    };

    struct ILoggerService : kgr::abstract_service<ILogger> {};
    struct LoggerService : kgr::single_service<Logger>, kgr::overrides<ILoggerService> {};

    struct User {
        User(kgr::container& ioc) : ioc_{ioc} {}
        kgr::container& ioc_;

        void f1() { ioc_.service<IAudioService>().playSound(1); }
        void f2() { ioc_.service<ILoggerService>().log("Hello"); }
    };

int main()
{
    kgr::container ioc;
    ioc.emplace<AudioService>();
    ioc.emplace<LoggerService>();
    User u(ioc);
    u.f1();
    u.f2();

    return 0;
}

如果我理解正确的话,此时它只是一个服务定位器,不是吗? 但是如果我有一些嵌套结构,如下所示:

    struct A {
        B b;
    }
    struct B {
        C c;
    }
    struct C {
        D d;
    }

,它应该被组合在一些Composition Root中,并且类A应该通过IoC容器创建,它将自动解决依赖关系。国际奥委会将在这方面发挥真正的优势,对吗?而且我仍然必须在任何需要服务的地方传递 IoC 容器。优点是为所有服务传递单个参数,而不是传递多个参数。

还有一件事:依赖注入(inject)以同样的方式适用于自由函数;如果我想在某些 void f() 中使用记录器,我应该通过参数传递 IoC 容器,或者直接在内部使用 - 在这种情况下没有依赖注入(inject)。但如果我不想让参数列表变得困惑,我别无选择。

最佳答案

使用库来处理依赖注入(inject)的主要优点是:

  • 样板代码的自动化
  • 拥有一个包含当前上下文实例的中心位置

使用依赖项注入(inject)容器,您将拥有包含所有实例的单个实体。将这个东西发送到任何地方可能很诱人,因为您将拥有可用的整个上下文,但我建议不要这样做。

在 kangaru 文档中,我在指南中添加了以下内容:

This library is a great tool to minimize coupling, but coupling with this library is still coupling.

例如,如果void f();(作为自由函数)需要记录器,那么它应该作为参数传递。

void f(ILogger& logger) {
    // ...
}

现在这就是依赖注入(inject)库的用武之地。是的,您可以使用容器来获取里面的内容并将其发送到函数,但它可能是很多样板文件:

f(ioc.service<ILogger>());

与您的用户类型相同,您将容器用作包含上下文的单个事物,而不使用它的样板减少功能。

最好的办法是让库最小化样板文件:

ioc.invoke(f);

kangaru 容器具有invoke 函数。您向它发送一个函数(例如对象或函数指针),它会自动注入(inject)参数。

User 类也是如此。最好的办法是在构造函数中接收必要的东西:

struct User {
    User(ILogger& l, IAudio& a) : logger{&l}, audio{&a} {}

    ILogger* logger;
    IAudio* audio;

    // You can use both logger and audio in the f1 and f2 functions
};

当然,它需要将 User 设为一项服务,但不是单一服务:

struct UserService : kgr::service<User, kgr::dependency<ILoggerService, IAudioService>> {};

现在,为非单个类定义这些服务可能看起来像样板文件,但如果您使用 kangaru 的新功能(例如服务映射),可以通过一些方法显着减少它:

// Define this beside the abstract services
// It maps A parameter (ILogger const& for example) to a service
auto service_map(ILogger const&) -> ILoggerService;
auto service_map(IAudio const&) -> IAudioService;

然后,您可以将服务声明为单行,使用服务映射实用程序生成服务:

struct User {
    User(ILogger& l, IAudio& a) : logger{&l}, audio{&a} {}

    ILogger* logger;
    IAudio* audio;

    // You can use both logger and audio in the f1 and f2 functions

    // Map a `User const&` parameter to a autowired service
    friend auto service_map(User const&) -> kgr::autowire;
};

然后,您可以为生成的服务命名:

// Optional step
using UserService = kgr::mapped_service_t<User const&>;

现在终于可以使用容器来生成实例了:

// Creates a new instance, since it's a non single
User newUser1 = ioc.service<User>();

// Or use a generator:
auto userGenerator = ioc.service<kgr::generator_service<UserService>>();

User newUser2 = userGenerator();
User newUser3 = userGenerator();

// Or use invoke:
ioc.invoke([](User user) {
    // user is generated instance
});

ioc.invoke([](kgr::generator<UserService> gen) {
    User newUser1 = gen();
    User newUser2 = gen();
});

正如您所注意到的,使用调用不一定需要定义服务,只需在需要服务的类中添加 friend auto service_map(...) -> kgr::autowire 即可构造函数以使其可与 invoke

一起使用

关于c++ - 如何正确使用IoC容器?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/67854366/

相关文章:

c# - 简易喷油器 : Factory classes that need to create classes with dependencies

c++ - C++(旧/新标准)中最大的数字原始数据类型是什么

c++ - get_input_port 方法位于 drake 的什么位置?

c++ - 转换和模板类型参数

mvvm - ViewModel 到 ViewModel 的通信

unity-container - 如何使用统一根据注入(inject)的类型有条件地绑定(bind)实例?

C++ 使用 unique_ptr<AbstractClass> 成员变量进行移动/复制/赋值

c# - 初始化后向 Container 添加注册表

dependency-injection - IOC 未解决依赖关系 - MVVMCross

java - 使用数组元素作为参数实例化对象java