我对 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/