如何正确使用 IoC 容器?
How to properly use IoC container?
我对 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中组合,class A
应该通过IoC-container创建,会自动解析依赖关系。 IoC 将在这里发挥真正的优势,对吗?而且我仍然必须在任何需要服务的地方传递 IoC-container。优点是为所有服务传递单个参数而不是传递多个参数。
还有一件事:依赖注入以同样的方式为自由函数工作;如果我想在某些 void f()
中使用记录器,我应该通过参数传递 IoC 容器,或者直接在内部使用 - 在这种情况下没有依赖注入。但如果我不想让参数列表混乱,我别无选择。
使用库处理依赖注入的主要优点是:
- 样板代码的自动化
- 有一个包含当前上下文实例的中心位置
使用依赖项注入容器,您拥有包含所有实例的单个实体。将那个东西发送到任何地方可能很诱人,因为你将拥有可用的整个上下文,但我建议不要这样做。
在袋鼠文档中,我在指南中添加了这个:
This library is a great tool to minimize coupling, but coupling with this library is still coupling.
因此,例如,如果 void f();
(作为自由函数)需要记录器,则应将其作为参数传递。
void f(ILogger& logger) {
// ...
}
现在这就是依赖注入库的用武之地。是的,您可以使用容器获取内部内容并将其发送到函数,但它可能有很多样板文件:
f(ioc.service<ILogger>());
与您的用户类型相同,您将容器用作包含上下文的单个事物,而不使用它的样板减少功能。
最好是让库最小化样板文件:
ioc.invoke(f);
袋鼠容器具有invoke
功能。你向它发送一个像对象或函数指针这样的函数,它会自动注入参数。
User
class 也是如此。最好是在构造函数中接收必要的东西:
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>> {};
现在,为非单一 classes 定义这些服务可能看起来像样板文件,但如果您使用 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();
});
如您所见,使用 invoke 不一定需要定义服务,只需在 class 中添加 friend auto service_map(...) -> kgr::autowire
即可,该 class 需要在其构造函数中使用服务,才能与 [=20= 一起使用]
我对 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中组合,class A
应该通过IoC-container创建,会自动解析依赖关系。 IoC 将在这里发挥真正的优势,对吗?而且我仍然必须在任何需要服务的地方传递 IoC-container。优点是为所有服务传递单个参数而不是传递多个参数。
还有一件事:依赖注入以同样的方式为自由函数工作;如果我想在某些 void f()
中使用记录器,我应该通过参数传递 IoC 容器,或者直接在内部使用 - 在这种情况下没有依赖注入。但如果我不想让参数列表混乱,我别无选择。
使用库处理依赖注入的主要优点是:
- 样板代码的自动化
- 有一个包含当前上下文实例的中心位置
使用依赖项注入容器,您拥有包含所有实例的单个实体。将那个东西发送到任何地方可能很诱人,因为你将拥有可用的整个上下文,但我建议不要这样做。
在袋鼠文档中,我在指南中添加了这个:
This library is a great tool to minimize coupling, but coupling with this library is still coupling.
因此,例如,如果 void f();
(作为自由函数)需要记录器,则应将其作为参数传递。
void f(ILogger& logger) {
// ...
}
现在这就是依赖注入库的用武之地。是的,您可以使用容器获取内部内容并将其发送到函数,但它可能有很多样板文件:
f(ioc.service<ILogger>());
与您的用户类型相同,您将容器用作包含上下文的单个事物,而不使用它的样板减少功能。
最好是让库最小化样板文件:
ioc.invoke(f);
袋鼠容器具有invoke
功能。你向它发送一个像对象或函数指针这样的函数,它会自动注入参数。
User
class 也是如此。最好是在构造函数中接收必要的东西:
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>> {};
现在,为非单一 classes 定义这些服务可能看起来像样板文件,但如果您使用 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();
});
如您所见,使用 invoke 不一定需要定义服务,只需在 class 中添加 friend auto service_map(...) -> kgr::autowire
即可,该 class 需要在其构造函数中使用服务,才能与 [=20= 一起使用]