boost-ext / di

C++14 Dependency Injection Library
https://boost-ext.github.io/di
1.13k stars 136 forks source link

[Question] Creating speciallized instancess of a class by overriding a dependency interface for different implementations #526

Open Tarjei400 opened 2 years ago

Tarjei400 commented 2 years ago

Hello, I could not find any meaningfull answer to my problem so I decided to ask a question here. This is just a simple example to ilustrate a problem, but I will refer to it for more clarity as if I wanted to use it.

So my problem is, I wanted to have multiple instances of Server class, however specialised in a way I am changing ICacheDriver interface (sometimes it might not be direct dependency, but even deeper in a tree, lets say I wanted to change ILogger upon instantiation). Below I am posting only solution I could come up with however it feels a little bit like workaround. More over if there is same interface (in this case ILogger) even though I expect shared instance to be injected, I always end up with new instances of ILogger.

Is below example only way of achieving this? I think there are no examples in documentation touching this subject. I hope description was clear enough, thank you for any clarifications on this matter.


#include <iostream>
#include <optional>
#include <memory>
#include <boost/di.hpp>
#include <boost/di/extension/bindings/contextual_bindings.hpp>

using namespace boost::ext;

namespace di = boost::di;

class Logger;
class RedisCacheDriver;
class MemCacheDriver;
class ICacheDriver;
class Logger;

struct Server {
    std::shared_ptr<ICacheDriver> cache{nullptr};
    BOOST_DI_INJECT(Server,
        const std::shared_ptr<ICacheDriver>& cache
    ): cache(cache) {
    }

    Server() {}
};

struct ICacheDriver {
    virtual void set() = 0;
};

struct RedisCacheDriver: public ICacheDriver{
    std::shared_ptr<Logger> log{nullptr};
    BOOST_DI_INJECT(RedisCacheDriver,
        const std::shared_ptr<Logger>& log
    ): log(log) {

    }
    void set() { std::cout << "Redis" << std::endl;}
};

struct MemCacheDriver: public ICacheDriver {
    std::shared_ptr<Logger> log{nullptr};

    BOOST_DI_INJECT(MemCacheDriver,
                    const std::shared_ptr<Logger>& log
    ): log(log) {

    }
    void set() { std::cout << "Redis" << std::endl;}
};

class Logger {
public:
    BOOST_DI_INJECT(Logger,
    ) {
    }
};

auto commonInjector = [](){
    return di::make_injector<di::extension::contextual_bindings>(
            di::bind<Server>().to<Server>(),
            di::bind<Logger>().to<Logger>()

    );
};
auto redisServerInjector = [](){

    return di::make_injector<di::extension::contextual_bindings>(
        di::bind<ICacheDriver>().to<RedisCacheDriver>()
    );
};

auto memCacheServerInjector = [](){
    return di::make_injector<di::extension::contextual_bindings>(
        di::bind<ICacheDriver>().to<MemCacheDriver>()
    );
};

struct RedisServer: public Server {
    RedisServer() = default;
};

struct MemcacheServer: public Server {
    MemcacheServer() = default;
};

int main() {

    auto redis_injector = di::make_injector(
        commonInjector(), redisServerInjector()
    );

    auto mem_cache_injector = di::make_injector(
        commonInjector(), memCacheServerInjector()
    );

    auto mainInjector = di::make_injector(
            di::bind<RedisServer>().to([&](const auto& _) -> std::shared_ptr<RedisServer>{
                auto ret = di::create<std::shared_ptr<Server>>(redis_injector);
                return std::static_pointer_cast<RedisServer>(ret);
            }),
            di::bind<MemcacheServer>().to([&](const auto& _) -> std::shared_ptr<MemcacheServer>{
                auto ret = di::create<std::shared_ptr<Server>>(mem_cache_injector);
                return std::static_pointer_cast<MemcacheServer>(ret);
            })
    );

    auto rServer = di::create<std::shared_ptr<RedisServer>>(mainInjector);
    auto mServer = di::create<std::shared_ptr<MemcacheServer>>(mainInjector);

    auto ptr = std::dynamic_pointer_cast<RedisCacheDriver>(rServer->cache);
    auto ptr2 = std::dynamic_pointer_cast<MemCacheDriver>(mServer->cache);

    std::cout << ptr.get() << std::endl;
    std::cout << ptr2.get() << std::endl;

}
krzysztof-jusiak commented 2 years ago

you can use [di::override] in any module to force resovling to the specific binding. For example,

di::bind<ICacheDriver>().to<MemCacheDriver>() [di::override]

should do it

Tarjei400 commented 2 years ago

I need to have both of these to be instantiated when application starts though, and I think there is no other way of achieving this other than using separate injectors right?

krzysztof-jusiak commented 2 years ago

you could use annotations for that - https://github.com/boost-ext/di/blob/cpp14/example/annotations.cpp or strong type interfaces

Tarjei400 commented 2 years ago

For having both instances (RedisServer and MemcacheServer) in same injector, yes I can use named injections, thats whats actually I am using in code, however even with [di::override] in order to instantiate those objects, I still need to have 2 seperate injectors right? That also mean that an object I am injecting as shared (Logger) will inject be instantiated 2 times aswell.

Tarjei400 commented 2 years ago

Hey so as @krzysztof-jusiak using annotations was part of it. I also dug up a little bit into extensible injectors, and shared_factory, which turned out to be a solution to my problem, when using shared_factory, even if using factories elsewhere, I can mark a deeper dependencies to be shared, apparently. I couldn't find it well described in docs I will leave below a full example of this, as I think docs a little insufficient on that matter. Thanks for help here!

#include <iostream>
#include <optional>
#include <memory>
#include <boost/di.hpp>
#include <boost/di/extension/bindings/contextual_bindings.hpp>
#include <boost/di/extension/injections/extensible_injector.hpp>
#include <boost/di/extension/injections/shared_factory.hpp>
#include <boost/di/extension/injections/factory.hpp>
#include <boost/di/extension/injections/assisted_injection.hpp>

using namespace boost::ext;

namespace di = boost::di;

class Logger;
class RedisCacheDriver;
class MemCacheDriver;
class ICacheDriver;
class Logger;

struct Server {
    std::shared_ptr<ICacheDriver> cache{nullptr};
    BOOST_DI_INJECT(Server,
        const std::shared_ptr<ICacheDriver>& cache
    ): cache(cache) {
    }

    Server() {}
};

struct ICacheDriver {
    std::shared_ptr<Logger> log{nullptr};

    virtual void set() = 0;
    ICacheDriver(const std::shared_ptr<Logger>& log): log(log) {}
};
struct NoCacheDriver: public ICacheDriver {
    void set() {};
};

struct RedisCacheDriver: public ICacheDriver{
    BOOST_DI_INJECT(RedisCacheDriver,
        const std::shared_ptr<Logger>& log
    ): ICacheDriver(log) {

    }
    void set() { std::cout << "Redis" << std::endl;}
};

struct MemCacheDriver: public ICacheDriver {

    BOOST_DI_INJECT(MemCacheDriver,
                    const std::shared_ptr<Logger>& log,
                    int v
    ): ICacheDriver(log) {

    }
    void set() { std::cout << "Memcache" << std::endl;}
};

class Logger {
public:
    BOOST_DI_INJECT(Logger,
    ) {
    }
};

struct RedisServer: public Server {
    RedisServer() {};
};

struct MemcacheServer: public Server {
    MemcacheServer() {};
};

struct TcpServer: public Server {
    TcpServer() = default;
};

auto Redis = [](){};
auto Memcache = [](){};

class Application {
public:
    std::shared_ptr<Server> tst;
    std::shared_ptr<Server> tst2;
    std::shared_ptr<Server> redis;
    std::shared_ptr<Server> memcache;

    BOOST_DI_INJECT(Application,

      (named = Redis) const std::shared_ptr<di::extension::ifactory<Server>>& factory,
      (named = Memcache) const std::shared_ptr<di::extension::ifactory<Server>>& factory2,
      (named = Redis) const std::shared_ptr<Server> redis,
      (named = Memcache) const std::shared_ptr<Server> memcache
    ): redis(redis), memcache(memcache){
        tst = factory->create();
        tst2 = factory2->create();

    }
};

int main() {

    auto commonInjector = di::make_injector<di::extension::contextual_bindings>(

            di::bind<Logger>().to(di::extension::shared_factory<Logger>([](const auto& injector) {
                auto ctx = di::extension::context(injector);
                std::cout << "new logger: "<< ctx << std::endl;
                return std::make_shared<Logger>();
            }))

            //di::bind<ICacheDriver>.to<NoCacheDriver>(),
//            di::bind<Application>.to<Application>()

    );

    auto redis_injector = di::make_injector<di::extension::contextual_bindings>(
        di::extension::make_extensible(commonInjector),
        di::bind<ICacheDriver>.to<RedisCacheDriver>()[di::override],
        di::bind<di::extension::ifactory<Server>>().to(di::extension::factory<Server>{})

    );

    auto mem_cache_injector = di::make_injector<di::extension::contextual_bindings>(
        di::extension::make_extensible(commonInjector),
        di::bind<ICacheDriver>.to<MemCacheDriver>()[di::override],
        di::bind<di::extension::ifactory<Server>>().to(di::extension::factory<Server>{})

    );

    auto f = di::create<di::extension::factory<Server>>(mem_cache_injector);
    using ServerInjector = std::function<std::unique_ptr<Server>()>;

    auto bootstrapInjector = di::make_injector<di::extension::contextual_bindings>(
            di::extension::make_extensible(commonInjector),

            di::bind<di::extension::ifactory<Server>>().named(Redis).to([&](){

                return di::create<std::shared_ptr<di::extension::ifactory<Server>>>(redis_injector);
            }),
            di::bind<di::extension::ifactory<Server>>().named(Memcache).to([&](){

                return di::create<std::shared_ptr<di::extension::ifactory<Server>>>(mem_cache_injector);
            }),

             di::bind<Server>().named(Redis).to([&](const auto& _) -> std::shared_ptr<Server>{
                auto ret = di::create<std::shared_ptr<Server>>(redis_injector);
                return ret;
            }),

            di::bind<Server>().named(Memcache).to([&](const auto& _) -> std::shared_ptr<Server>{
                auto ret = di::create<std::shared_ptr<Server>>(mem_cache_injector);
                return ret;
            })

    );

//    auto srv = di::create<std::shared_ptr<Server>>(redis_injector);
//    auto srv2 = di::create<std::shared_ptr<Server>>(mem_cache_injector);
    auto app = di::create<std::shared_ptr<Application>>(bootstrapInjector);

    std::cout << "Created " << (app->redis->cache->log == app->memcache->cache->log) << std::endl;
    std::cout << "created 2 " << (app->tst->cache->log == app->memcache->cache->log) << std::endl;
    std::cout << "created 3 " << (app->tst->cache->log == app->redis->cache->log) << std::endl;
    std::cout << "created 4 " << (app->tst->cache->log == app->tst2->cache->log) << std::endl;

    assert(app->tst != app->redis);
    assert(app->tst2 != app->memcache);
    assert(app->tst->cache->log == app->redis->cache->log);
    assert(app->tst->cache->log == app->memcache->cache->log);
    assert(app->tst2->cache->log == app->redis->cache->log);
    assert(app->tst2->cache->log == app->memcache->cache->log);

    app->tst->cache->set();
    app->tst2->cache->set();
    app->redis->cache->set();
    app->memcache->cache->set();
}