[英]Best practices for dependency injection via constructor
class object
{
public:
object(dependency d) : dep_(d) {}
private:
dependency dep_;
};
仅适用于dependency
类完全无状态的情况,即没有任何成员。 实际上,这种情况很少发生,因为dependency
类可能存储自己的依赖关系。
class object
{
public:
object(dependency *d) : dep_(d)
{
if (d == nullptr)
throw std::exception("null dependency");
}
private:
dependency *dep_;
};
这就像真正的注射一样。 我们需要检查传递的指针是否为nullptr
值。
object
类不拥有dependency
类,因此调用代码以确保在dependency
对象之前销毁object
是责任。
在实际应用中,有时很难验证。
#define DISALLOW_COPY_AND_ASSIGN(Class) \
Class(const Class &) = delete; \
Class &operator=(const Class &) = delete
class object
{
public:
object(dependency &d) : dep_(d) {}
DISALLOW_COPY_AND_ASSIGN(object);
private:
dependency &dep_;
};
引用不能为空,因此在此预期中更安全一些。
但是,这种方法会给object
类带来额外的约束:它必须是不可复制的,因为无法复制引用。 您必须手动覆盖赋值运算符和复制构造函数以停止复制或从boost::noncopyable
继承它。
与原始指针一样,所有权约束已到位。 调用代码应为两个类提供正确的销毁顺序,否则引用将变为无效,应用程序将因访问冲突而崩溃。
如果依赖项是const引用:
class object
{
public:
object(const dependency &d) : dep_(d) {}
private:
const dependency &dep_;
};
你应该注意object
类接受对临时对象的引用这一事实:
dependency d;
object o1(d); // this is ok, but...
object o2(dependency()); // ... this is BAD.
更多详情:
class object
{
public:
object(std::shared_ptr<dependency> d) : dep_(d)
{
if (!d)
throw std::exception("null dependency");
}
private:
std::shared_ptr<dependency> dep_;
};
与原始指针类似,但所有权由智能指针机制控制。
仍然需要在构造函数体中检查nullptr
。
主要优点是dependency
对象生存期控制:调用应用程序不需要正确控制销毁顺序(但考虑到在使用std::shared_ptr
设计API时需要非常小心 )。
一旦不再使用dependency
类,它就会被shared_ptr
析构函数自动销毁。
有些情况下, shared_ptr
拥有的对象不会被销毁(所谓的循环引用 )。 但是,使用构造函数注入时,由于特定的明确定义的构造顺序,循环依赖性是不可能的。
如果在整个应用程序中没有使用其他注入方法,这当然有效。
智能指针的开销很小,但在大多数情况下并不是真正的问题。
更多详情:
这是一个老问题,但对我来说这是一个热门话题,因为我在所有 web 我能听到的框架中都发现了自动依赖注入魔法,它们通常是用内省的恶作剧构建的,我总是很高兴发现它们的实现。 但是我在 C++ 中找不到一个简单的方法来做同样的事情。
服务定位器方法确实可以很好地解决这个问题,但是在构造函数中声明依赖关系并在两者之间摆脱这种模式似乎更干净,使用起来更灵活,因为它更容易实例化您的类,传递不同的服务实例。
但是服务定位器方法也可以处理循环依赖,因为它们可以被懒惰地挑选,有时循环依赖会发生(可能只在糟糕的代码中)。
不幸的是,我还没有找到在构造函数中检测 arguments 类型并自动注入此类实例的方法。
无论如何,我想分享我迄今为止发现的在类中自动注入依赖项的最佳解决方案。 它类似于一个服务定位器,将其服务处理为带有智能指针的 singleton 并可用于依赖注入,但必须对其进行修改以允许具有某些共同依赖关系的两个类获得相同类型的不同实例。
template<typename T>
struct di_explicit
{
static std::shared_ptr<T> ptr;
virtual ~di_explicit()
{
if(di_explicit<T>::ptr.use_count() == 1) {
reset();
}
}
virtual std::shared_ptr<T> get()
{
return di_explicit<T>::ptr;
}
static void reset()
{
di_explicit<T>::ptr.reset();
}
static void swap(std::shared_ptr<T> arg)
{
arg.swap(di_explicit<T>::ptr);
}
static void emplace(auto && ... args)
{
swap(std::make_shared<T>(std::forward(args) ...));
}
static void emplace_if_not_exists(auto && ... args)
{
if(!di_explicit<T>::ptr) {
emplace(std::forward(args) ...);
}
}
};
template<typename T>
std::shared_ptr<T> di_explicit<T>::ptr {};
template<typename T>
struct di : di_explicit<T>
{
di(auto && ... args)
{
di_explicit<T>::emplace_if_not_exists(std::forward(args) ...);
}
};
template<typename T>
struct di_lazy : di_explicit<T>
{
auto get(auto && ... args)
{
di_explicit<T>::emplace_if_not_exists(std::forward(args) ...);
return di_explicit<T>::ptr;
}
};
上面代码片段背后的想法是:
它是一个处理另一个 class 的 memory 的逻辑包装器,这样的包装器能够自动创建托管 class 的实例,并在请求时将引用作为 singleton 传递,当不再有对托管的引用时,memory 将自动释放object。
可以使用托管 class(或子类型)的特定实例,以便用户可以声明对所需服务接口的依赖性,并在程序运行时或在测试期间模拟时实例化具体依赖性。
在循环依赖的情况下,有一种方法可以延迟实例化所需的依赖。
基本逻辑编码在基础 class di_explicit<T>
中,它使用static shared_ptr<T>
来创建单例,以及一个析构函数,当最后一个引用是 static 时重置共享指针(存储在di_explicit<T>
).
struct di: di_explicit<T>
在其构造函数中检索依赖关系,而di_lazy: di_explicit<T>
仅在请求依赖关系时(在 get() 方法中)执行此操作。
以下是带有模拟的示例(非惰性)。
namespace {
struct dependency {
virtual void do_something() {
std::cout << "doing something" << std::endl;
}
};
struct mock : dependency {
using dependency::do_something;
void do_something() {
std::cout << "mocking something" << std::endl;
}
};
struct srv {
di<dependency> dep;
void do_stuff() {
std::cout << "doing stuff" << std::endl;
return dep.get()->do_something();
}
};
int test = [](){
// the classes are not instanciated yet
std::cout << "ptr exists " << !!di<srv>::ptr << std::endl;
{
// the classes instanciated here
di<srv> s;
s.get()->do_stuff();
std::cout << "ptr exists " << !!di<srv>::ptr << std::endl;
} // <- the instances are destroyed here
std::cout << "ptr exists " << !!di<srv>::ptr << std::endl;
{
// use a mock instance
di_explicit<dependency>::swap(std::make_shared<mock>());
di<srv>{}.get()->do_stuff();
} // <- the mock is destroyed here too
std::cout << "ptr exists " << !!(di<dependency>::ptr) << std::endl;
return 0;
}();
}
下面是一个带有循环引用和 di_lazy 的例子。
namespace {
struct dep_2;
struct dep_3;
struct dep_1 {
di_lazy<dep_2> dep;
void do_something();
};
struct dep_2 {
di_lazy<dep_3> dep;
void do_something();
};
struct dep_3 {
di_lazy<dep_1> dep;
void do_something() {
std::cout << "dep_3 do_something" << std::endl;
dep.get()->do_something();
}
virtual void do_something_else() {
std::cout << "dep_3 do_something_else" << std::endl;
}
};
void dep_1::do_something() {
std::cout << "dep_1 do_something" << std::endl;
dep.get()->do_something();
}
void dep_2::do_something() {
std::cout << "dep_2 do_something" << std::endl;
dep.get()->do_something_else();
}
struct srv_2 {
di<dep_3> dep;
void do_something() {
std::cout << "srv_2 do_something" << std::endl;
return dep.get()->do_something();
}
};
int result = [](){
{
// neither the dependencies or the service are requested yet
di_lazy<srv_2> wrapper{};
// here the service is requested
auto s = wrapper.get();
// dependencies are requested inside this function
s->do_something();
}
{
struct mock_dep_3 : dep_3 {
virtual void do_something_else() {
std::cout << "dep_3 do_something_else MOCKED!" << std::endl;
}
};
// a mock can be used with di_lazy as well
di_explicit<dep_3>::swap(std::make_shared<mock_dep_3>());
di<srv_2>{}.get()->do_something();
}
return 0;
}();
}
我知道还有改进的余地(任何建议都表示赞赏),我希望你觉得它有用
我找到了一个更好的方法来做同样的事情,但这次扩展了std::shared_ptr
class 本身。
它仍然是某种服务定位器,但使用以下代码片段也可以在构造函数中将共享指针作为 arguments 传递
template<typename T>
class di : public std::shared_ptr<T>
{
static std::shared_ptr<T> ptr;
public:
static void reset()
{
di<T>::ptr.reset();
}
static di<T> replace(std::shared_ptr<T> ptr)
{
di<T>::ptr = ptr;
return di<T>::ptr;
}
template<typename ... args_t>
static di<T> emplace(args_t && ... args)
{
return di<T>::replace(std::make_shared<T>(
std::forward<args_t>(args) ...
));
}
static di<T> instance()
{
return di<T>::ptr;
}
~di()
{
if(this->is_linked() && di<T>::ptr.use_count() <= 2){
di<T>::ptr.reset();
}
}
bool is_linked()
{
return *this && di<T>::ptr.get() == this->get();
}
template<typename ... args_t>
di(args_t && ... ptr) : std::shared_ptr<T>(std::forward<args_t>(ptr) ...)
{}
};
template<typename T>
std::shared_ptr<T> di<T>::ptr {};
使用此 class,您可以使用构造函数将某些服务的实例传递给另一个
IE
struct logger_interface
{
virtual void log(std::string) = 0;
virtual ~logger_interface() = default;
};
struct some_service_interface
{
virtual void serve() = 0;
virtual ~some_service_interface() = default;
};
struct logger_with_id : logger_interface
{
static int counter;
int id = ++counter;
void log(std::string s) {
std::cout << id << ") " << s << std::endl;
}
};
int logger_with_id::counter = 0;
struct some_service : some_service_interface
{
di<logger_interface> logger;
some_service(
di<logger_interface> logger = di<logger_interface>::instance()
) :
logger(logger)
{}
void serve() {
logger->log("serving...");
}
};
int app = []() {
di<logger_interface>::replace(di<logger_with_id>::emplace());
di<some_service_interface>::replace(di<some_service>::emplace());
std::cout << "running app"<< std::endl;
di<logger_interface>::instance()->log("app");
di<some_service_interface>::instance()->serve();
std::cout << std::endl;
return 0;
}();
将打印
running app
1) app
1) serving...
如果你需要,你可以覆盖某些服务的依赖
struct decorated_logger : logger_interface {
di<logger_interface> logger;
decorated_logger(
di<logger_interface> logger = di<logger_interface>::instance()
) :
logger(logger)
{}
void log(std::string s) {
logger->log("decorating...");
logger->log(s);
}
};
int app_with_custom_logger_on_service = [](
di<logger_interface> logger,
di<some_service_interface> service
) {
std::cout << "running app_with_custom_logger_on_service"<< std::endl;
logger->log("app");
service->serve();
std::cout << std::endl;
return 0;
}(
di<logger_interface>::replace(std::make_shared<logger_with_id>()),
di<some_service_interface>::replace(std::make_shared<some_service>(
std::make_shared<decorated_logger>(std::make_shared<logger_with_id>())
))
);
将打印
running app_with_custom_logger_on_service
2) app
3) decorating...
3) serving...
这也可以用于测试
struct mock_logger : logger_interface {
void log(std::string) {
std::cout << "mock_logger" << std::endl;
}
};
struct mock_some_service : some_service_interface {
void serve() {
std::cout << "mock_some_service" << std::endl;
}
};
int test = [](
di<logger_interface> logger,
di<some_service_interface> service
) {
std::cout << "running test"<< std::endl;
logger->log("app");
service->serve();
std::cout << std::endl;
return 0;
}(
di<logger_interface>::replace(std::make_shared<mock_logger>()),
di<some_service_interface>::replace(std::make_shared<mock_some_service>())
);
将打印
running test
mock_logger
mock_some_service
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.