Provides simple, yet useful dependency injection mechanism.
Introduction
Let's start with showing usage, for example class Foo:
class Foo {
public:
Foo();
void do_something();
};
We want DIM to make instance(s) of this class available throughout our application.
Scenario 1: when Foo is a singleton
void init_code() {
DIM::instance().set_Foo([](){ return new Foo; });
}
void use_code() {
Foo& foo = DIM::instance().get_Foo();
foo.do_something();
foo.do_something();
foo.do_something();
}
Scenario 2: when Foo is not a singleton
void init_code() {
DIM::instance().set_Foo([](){ return new Foo; });
}
void use_code() {
UniquePtr<Foo> foo1 = DIM::instance().new_Foo();
foo1->do_something();
UniquePtr<Foo> foo2 = DIM::instance().new_Foo();
foo2->do_something();
UniquePtr<Foo> foo3 = DIM::instance().new_Foo();
foo3->do_something();
}
Scenario 3: when Foo already exists (typically used in unit tests)
Foo foo_that_lives_forever;
void init_code() {
DIM::instance().set_Foo(
[](){
return &foo_that_lives_forever;
},
[](Foo*) {});
}
void use_code() {
Foo& foo = DIM::instance().get_Foo();
foo.do_something();
}
Convenient, isn't it? But to make all this happen, class Foo (boilerplate code) has to be added to DIM class.
Usage
Adding a new managed object is done in 4 steps:
- add class forward declaration
- add object factory + deleter setter
- add singleton object getter or object creator. Adding both usually makes no sense
- add factory and deleter function objects
Here is the (relevant part of) class DIM for class Foo:
class Foo;
class DIM {
public:
void set_Foo(const std::function<Foo*(void)>& factory,
const std::function<void(Foo*)>& deleter =
std::default_delete<Foo>()) {
factory_Foo_ = factory; deleter_Foo_ = deleter;
}
Foo& get_Foo() const {
return get_generic<Foo>(factory_Foo_, deleter_Foo_);
}
UniquePtr<Foo> new_Foo() const {
return new_generic(factory_Foo_, deleter_Foo_);
}
private:
std::function<Foo*(void)> factory_Foo_;
std::function<void(Foo*)> deleter_Foo_;
};
Example
class Foo;
class Bar;
class Baz;
class DIM {
public:
void set_Foo(const std::function<Foo*(void)>& factory,
const std::function<void(Foo*)>& deleter =
std::default_delete<Foo>()) {
factory_Foo_ = factory; deleter_Foo_ = deleter;
}
void set_Bar(const std::function<Bar*(void)>& factory,
const std::function<void(Bar*)>& deleter =
std::default_delete<Bar>()) {
factory_Bar_ = factory; deleter_Bar_ = deleter;
}
void set_Baz(const std::function<Baz*(void)>& factory,
const std::function<void(Baz*)>& deleter =
std::default_delete<Baz>()) {
factory_Baz_ = factory; deleter_Baz_ = deleter;
}
Foo& get_Foo() const {
return get_generic<Foo>(factory_Foo_, deleter_Foo_);
}
Bar& get_Bar() const {
return get_generic<Bar>(factory_Bar_, deleter_Bar_);
}
Baz& get_Baz() const {
return get_generic<Baz>(factory_Baz_, deleter_Baz_);
}
UniquePtr<Foo> new_Foo() const {
return new_generic(factory_Foo_, deleter_Foo_);
}
UniquePtr<Bar> new_Bar() const {
return new_generic(factory_Bar_, deleter_Bar_);
}
UniquePtr<Baz> new_Baz() const {
return new_generic(factory_Baz_, deleter_Baz_);
}
private:
std::function<Foo*(void)> factory_Foo_;
std::function<void(Foo*)> deleter_Foo_;
std::function<Bar*(void)> factory_Bar_;
std::function<void(Bar*)> deleter_Bar_;
std::function<Baz*(void)> factory_Baz_;
std::function<void(Baz*)> deleter_Baz_;
};
struct Baz {
Baz() {}
};
struct Bar {
Bar(Baz, int) {}
};
struct Foo {
Foo(Bar, Baz) {}
void do_something() {}
};
DIM& dim = DIM::instance();
dim.set_Foo([&dim]() {
return new Foo(dim.get_Bar(), dim.get_Baz()); });
dim.set_Bar([&dim,
n]() {
return new Bar(dim.get_Baz(),
n); });
dim.set_Baz([]() {
return new Baz; });
dim.get_Foo().do_something();
UniquePtr<Foo> foo = dim.new_Foo();
foo->do_something();
}
int main(int argc, char **argv)
Definition: mysqlcheck.cc:521
int n
Definition: xcom_base.cc:509
Object Reset
There's also an option to reset an object managed by DIM, should you need it. Normally, on the first call to get_Foo(), it will call the factory_Foo_() to create the object before returning it. On subsequent calls, it will just return that Foo object previously created. But what if you needed to reset that object? And perhaps to create it via another Foo factory method, or with different parameters?
For such case, we can define reset_Foo() method, which will reset the Foo object back to nullptr. The Foo object can no longer be kept inside of get_Foo(), because it has to be modifiable via reset_Foo(). Here's the code:
std::function<Foo*(void)> factory_Foo_;
std::function<void(Foo*)> deleter_Foo_;
UniquePtr<Foo> instance_Foo_;
Foo& get_Foo() {
return get_external_generic(instance_Foo_,
factory_Foo_,
deleter_Foo_);
}
void reset_Foo() { reset_generic(instance_Foo_); }
void set_Foo(const std::function<Foo*(void)>& factory,
const std::function<void(Foo*)>& deleter =
std::default_delete<Foo>()) {
factory_Foo_ = factory;
deleter_Foo_ = deleter;
}
Example
DIM& dim = DIM::instance();
dim.set_Foo([]() { return new Foo(42); });
dim.get_Foo().do_something();
dim.get_Foo().do_something();
dim.get_Foo().do_something();
dim.set_Foo([]() {
return new Foo(555);
});
dim.get_Foo().do_something();
dim.reset_Foo();
dim.get_Foo().do_something();