Info
-
Did you know about different ways of constructor Dependency Injection?
Example
class iapi {
public:
virtual ~iapi() = default;
virtual auto call() const -> int { return 42; }
};
class app_interface {
public:
constexpr explicit(true) app_interface(const iapi& api) : api_{api} {}
auto run() const -> int { return api_.call(); }
private:
const iapi& api_;
};
template<class TApi>
class app_template {
public:
constexpr explicit(true) app_template(const TApi& api) : api_{api} {}
auto run() const -> int { return api_.call(); }
private:
const TApi& api_;
};
template<class T>
concept api = requires(T t) {
t.call();
};
template<api TApi>
class app_concept {
public:
constexpr explicit(true) app_concept(const TApi& api) : api_{api} {}
auto run() const -> int { return api_.call(); }
private:
const TApi& api_;
};
int main() {
// interface injection
{
struct : iapi {
auto call() const -> int override { return 42; }
} fake_api;
app_interface app{fake_api};
assert(42 == app.run());
}
// template injection
{
iapi api{};
app_template app{api};
assert(42 == app.run());
}
// concept injection
{
iapi api{};
app_concept app{api};
assert(42 == app.run());
}
}
Puzzle
-
Can you implement required steps with injecting
api
via {interface, template, concept}?- Double points for injection via type erasure!
class iapi {
public:
virtual ~iapi() = default;
virtual auto call() const -> int = 0;
};
template<class T>
concept api = requires(const T& t) {
{ t.call() } -> std::same_as<int>;
};
class app_interface; // TODO
template<class TApi> class app_template; // TODO
template<api TApi> class app_concept; // TODO
// NOTE: Double points for injection via type erasure!
int main() {
using namespace boost::ut;
bdd::gherkin::steps steps = [](auto& steps) {
steps.feature("Dependency Injection*") = [&] {
steps.scenario("*") = [&] {
steps.given("I have an api which returns {}") = [&](int value) {
// TODO
steps.given("I have an app interface") = [&] {
// TODO
auto run_result = 0;
steps.when("I call run on the app") = [&] {
run_result = app.run();
};
steps.then("I should get {} from app call") = [&](_i result) {
expect(run_result == result);
};
};
steps.given("I have an app template") = [&] {
// TODO
auto run_result = 0;
steps.when("I call run on the app") = [&] {
run_result = app.run();
};
steps.then("I should get {} from app call") = [&](_i result) {
expect(run_result == result);
};
};
steps.given("I have an app concept") = [&] {
// TODO
auto run_result = 0;
steps.when("I call run on the app") = [&] {
run_result = app.run();
};
steps.then("I should get {} from app call") = [&](_i result) {
expect(run_result == result);
};
};
};
};
};
};
"app"_test = steps | R"(
Feature: Dependency Injection
Scenario: Via interface
Given I have an api which returns 10
Given I have an app interface
When I call run on the app
Then I should get 10 from app call
Scenario: Via template
Given I have an api which returns 100
Given I have an app template
When I call run on the app
Then I should get 100 from app call
Scenario: Via concept
Given I have an api which returns 1000
Given I have an app concept
When I call run on the app
Then I should get 1000 from app call
)";
}
Solutions
class app_interface{
const iapi& api_;
public:
app_interface(const iapi& api) : api_{api} {}
auto run() const {
return api_.call();
}
};
template<class TApi> class app_template {
const TApi& api_;
public:
app_template(const TApi& api) : api_{api} {}
auto run() const {
return api_.call();
}
};
template<api TApi> class app_concept {
const TApi& api_;
public:
app_concept(const TApi& api) : api_{api} {}
auto run() const {
return api_.call();
}
};
class app_erasure {
const std::any api_;
public:
app_erasure(const auto& api) : api_{api} {}
auto run() const {
return std::any_cast<const iapi&>(api_).call();
}
};
struct fake_api : iapi {
fake_api(int value):value_{value}{}
int value_{};
virtual int call() const override {
return value_;
}
};
bdd::gherkin::steps steps = [](auto& steps) {
steps.feature("Dependency Injection*") = [&] {
steps.scenario("*") = [&] {
steps.given("I have an api which returns {}") = [&](int value) {
const fake_api fake_{value};
steps.given("I have an app interface") = [&] {
const app_interface app{fake_};
auto run_result = 0;
steps.when("I call run on the app") = [&] {
run_result = app.run();
};
steps.then("I should get {} from app call") = [&](_i result) {
expect(run_result == result);
};
};
steps.given("I have an app template") = [&] {
const app_template<fake_api> app{fake_};
auto run_result = 0;
steps.when("I call run on the app") = [&] {
run_result = app.run();
};
steps.then("I should get {} from app call") = [&](_i result) {
expect(run_result == result);
};
};
steps.given("I have an app concept") = [&] {
const app_concept<fake_api> app{fake_};
auto run_result = 0;
steps.when("I call run on the app") = [&] {
run_result = app.run();
};
steps.then("I should get {} from app call") = [&](_i result) {
expect(run_result == result);
};
};
steps.given("I have an app app erasure") = [&] {
const app_erasure app{fake_};
auto run_result = 0;
steps.when("I call run on the app") = [&] {
run_result = app.run();
};
steps.then("I should get {} from app call") = [&](_i result) {
expect(run_result+1 == result);
};
};
};
};
};
};
class app_interface
{
public:
app_interface( iapi const & p_api ):m_api(p_api){}
auto run(){ return m_api.call();}
iapi const & m_api;
};
template<class TApi> class app_template
{
public:
app_template(TApi const & p_api):m_api(p_api){}
auto run(){ return m_api.call();}
TApi const& m_api;
};
template<api TApi> class app_concept
{
public:
app_concept(TApi const & p_api):m_api(p_api){}
auto run(){ return m_api.call();}
TApi const& m_api;
};
bdd::gherkin::steps steps = [](auto& steps) {
steps.feature("Dependency Injection*") = [&] {
steps.scenario("*") = [&] {
steps.given("I have an api which returns {}") = [&](int value) {
struct fake_api:public iapi
{
fake_api(int v):v(v){}
int call() const override{ return v; }
int v;
};
fake_api my_api(value);
steps.given("I have an app interface") = [&] {
app_interface app{my_api};
auto run_result = 0;
steps.when("I call run on the app") = [&] {
run_result = app.run();
};
steps.then("I should get {} from app call") = [&](_i result) {
expect(run_result == result);
};
};
steps.given("I have an app template") = [&] {
app_template app{my_api};
auto run_result = 0;
steps.when("I call run on the app") = [&] {
run_result = app.run();
};
steps.then("I should get {} from app call") = [&](_i result) {
expect(run_result == result);
};
};
steps.given("I have an app concept") = [&] {
app_concept app{my_api};
auto run_result = 0;
steps.when("I call run on the app") = [&] {
run_result = app.run();
};
steps.then("I should get {} from app call") = [&](_i result) {
expect(run_result == result);
};
};
};
};
};
};
class app_interface {
public:
app_interface(const iapi& a) : api_{a} {}
const auto run() const { return api_.call(); }
private:
const iapi& api_;
};
template <api TApi> class app_template {
public:
app_template(const TApi& a) : api_{a} {}
const auto run() const { return api_.call(); }
private:
const TApi& api_;
};
class app_concept {
public:
template <api TApi>
app_concept(const TApi& a) : api_{std::make_unique<erased_api<TApi>>(a)} {}
const auto run() const { return api_->call(); }
private:
template <api TApi>
struct erased_api : iapi {
erased_api(const TApi& a) : api_{a} {}
auto call() const -> int override { return api_.call(); }
TApi api_;
};
std::unique_ptr<iapi> api_;
};
bdd::gherkin::steps steps = [](auto& steps) {
steps.feature("Dependency Injection*") = [&] {
steps.scenario("*") = [&] {
steps.given("I have an api which returns {}") = [&](int value) {
struct local_api : iapi {
local_api(int value) : value_{value} {}
auto call() const -> int override { return value_; }
int value_;
} a{value};
steps.given("I have an app interface") = [&] {
const auto app = app_interface{a};
auto run_result = 0;
steps.when("I call run on the app") = [&] {
run_result = app.run();
};
steps.then("I should get {} from app call") = [&](_i result) {
expect(run_result == result);
};
};
steps.given("I have an app template") = [&] {
const auto app = app_template{a};
auto run_result = 0;
steps.when("I call run on the app") = [&] {
run_result = app.run();
};
steps.then("I should get {} from app call") = [&](_i result) {
expect(run_result == result);
};
};
steps.given("I have an app concept") = [&] {
const auto app = app_concept{a};
auto run_result = 0;
steps.when("I call run on the app") = [&] {
run_result = app.run();
};
steps.then("I should get {} from app call") = [&](_i result) {
expect(run_result == result);
};
};
};
};
};
};
class app_interface {
public:
constexpr explicit(true) app_interface(const iapi& api) : api_{api} {}
auto run() const -> int { return api_.call(); }
private:
const iapi& api_;
};
template<class TApi> class app_template {
public:
constexpr explicit(true) app_template(const TApi& api) : api_{api} {}
auto run() const -> int {return api_.call(); }
private:
const TApi& api_;
};
template<api TApi> class app_concept {
public:
constexpr explicit(true) app_concept(const TApi& api) : api_{api} {}
auto run() const -> int { return api_.call(); }
private:
const TApi& api_;
};
bdd::gherkin::steps steps = [](auto& steps) {
steps.feature("Dependency Injection*") = [&] {
steps.scenario("*") = [&] {
steps.given("I have an api which returns {}") = [&](int value) {
api1 myApi(value);
steps.given("I have an app interface") = [&] {
app_interface app(myApi);
auto run_result = 0;
steps.when("I call run on the app") = [&] {
run_result = app.run();
};
steps.then("I should get {} from app call") = [&](_i result) {
expect(run_result == result);
};
};
steps.given("I have an app template") = [&] {
app_template app(myApi);
auto run_result = 0;
steps.when("I call run on the app") = [&] {
run_result = app.run();
};
steps.then("I should get {} from app call") = [&](_i result) {
expect(run_result == result);
};
};
steps.given("I have an app concept") = [&] {
app_concept app(myApi);
auto run_result = 0;
steps.when("I call run on the app") = [&] {
run_result = app.run();
};
steps.then("I should get {} from app call") = [&](_i result) {
expect(run_result == result);
};
};
};
};
};
};