Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
Tarjei400 opened this issue Aug 17, 2021 · 5 comments

Comments

@Tarjei400
Copy link

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
Copy link
Collaborator

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
Copy link
Author

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
Copy link
Collaborator

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

@Tarjei400
Copy link
Author

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
Copy link
Author

Tarjei400 commented Sep 8, 2021

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();
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants