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

Autowiring of factory methods #43

Open
dgehri opened this issue Jan 16, 2020 · 2 comments
Open

Autowiring of factory methods #43

dgehri opened this issue Jan 16, 2020 · 2 comments

Comments

@dgehri
Copy link

dgehri commented Jan 16, 2020

In order to achieve full dependency inversion, I don't want to expose the concrete classes from code modules, but rather factory methods creating concrete objects implementing a given interface. They look like this:

struct IFoo { ... };
struct IBar { ... };

struct FooFactory
{
    static std::unique_ptr<IFoo> createInstance(std::shared_ptr<IBar>);
    FooFactory() = delete;
};

Unfortunately, registering the factory is very verbose:

builder.registerInstanceFactory([=](Hypodermic::ComponentContext& context) 
    -> std::shared_ptr<IFoo> {
        return FooFactory::createInstance(context.resolve<IBar>());
        });

(1) Would it be possible to provide auto-wiring for factory methods, just as for constructors, something likes this:

builder.registerInstanceFactory(FooFactory::createInstance).as<IFoo>();

NOTE: the factories return std::unique_ptr not std::shared_ptr because they are IoC (Hypodermic) agnostic. Converting from the former to the later is trivial. But still, Hypodermic currently requires one to force that conversion (-> std::shared_ptr<IFoo>). Would be nice if that was implicit.

@ybainier
Copy link
Owner

I could write it for one argument but I don't know how to write the generalisation to n arguments and I am not aware of a possible way to perform this trick.
The basic idea behind one argument would be to deduce both the argument and the return type then encapsulate the call inside a lambda expression accepting a ComponentContext to finally register it via registerInstanceFactory.

Maybe you could change your factory so that it gets injected a IBar. Something like this:

class FooFactory
{
    FooFactory(std::shared_ptr< IBar >);

    std::unique_ptr< IFoo > createInstance();
};

class ReliesOnFoo
{
    ReliesOnFoo(std::shared_ptr< FooFactory >);

    void doSomething()
    {
        auto foo = fooFactory->createInstance();
    }
};

ContainerBuilder builder;
builder.registerType< Bar >().as< IBar >();
builder.registerType< FooFactory >();

auto container = builder.build();
auto reliesOnFoo = container->resolve< ReliesOnFoo >();

Would that work for you?

@dgehri
Copy link
Author

dgehri commented Jan 22, 2020

Thank you for your reply! Unfortunately, I'm not convinced by your solution, as it forces the ReliesOnFoo onto clients. Anyway, I managed to implement what I intended to do.

See running code on godbolt.org.

Using this, one can now register a factory method taking any number of shared_ptrs simply using builder.registerInstanceFactory(FooFactory::createInstance). Would it be possible to add this to Hypodermic?

Note: currently, I'm overloading ContainerBuilder::registerInstanceFactory, which requires a bit of SFINAE to distinguish between the original method taking a ComponentContext, and the new one expecting any number of shared_ptr parameters. If this isn't desired, simply changing the method name is sufficient.

--Daniel

Code Example:

#include <Hypodermic/ContainerBuilder.h>
#include <iostream>
#include <type_traits>

struct IBar {};
struct Bar : IBar {};
struct IBoo {};
struct Boo : IBoo {};

struct IFoo {};
struct IFoo2 : IFoo {};
struct Foo : IFoo2
{
    Foo(std::shared_ptr<IBar> bar, std::shared_ptr<IBoo> boo) {}
};

struct FooFactory
{
    static std::unique_ptr<IFoo2> createInstance(std::shared_ptr<IBar> bar, std::shared_ptr<IBoo> boo)
    {
        std::cout << "Creating instance of Foo" << std::endl;
        return std::make_unique<Foo>(bar, boo);
    }
};

class ContainerBuilder : public Hypodermic::ContainerBuilder
{
public:
    using Hypodermic::ContainerBuilder::ContainerBuilder;

    template <class TCallable,
              typename = std::enable_if_t<std::is_invocable_v<TCallable, Hypodermic::ComponentContext&>>>
    auto& registerInstanceFactory(const TCallable& instanceFactory)
    {
        return Hypodermic::ContainerBuilder::registerInstanceFactory(instanceFactory);
    }
   
    template <class FactoryFuncT,
              typename = std::enable_if_t<!std::is_invocable_v<FactoryFuncT, Hypodermic::ComponentContext&>>>
    auto& registerInstanceFactory(FactoryFuncT factoryFunc)
    {
        return registerInstanceFactory([factoryFunc](Hypodermic::ComponentContext& ctxt) {
            return FactoryDeducer<FactoryFuncT>::createInstance(factoryFunc, ctxt);
        });
    }

private:
    // Declare FactoryDeducer
    template <class Sig> struct FactoryDeducer;

    // Specialize for functions R f(std::shared_ptr<...>)
    template <class R, class ...Args>
    struct FactoryDeducer<R(*)(std::shared_ptr<Args>...)>
    {
        using FactoryFuncT = R(*)(std::shared_ptr<Args>...);
        static auto createInstance(FactoryFuncT factoryFunc, Hypodermic::ComponentContext& ctxt)
            -> std::shared_ptr<typename R::element_type>
        {
            return factoryFunc(ctxt.resolve<Args>()...);
        }
    };
};

void main()
{
    ContainerBuilder builder{};
    builder.registerInstanceFactory(FooFactory::createInstance).as<IFoo>().asSelf();

    auto container = builder.build();

    auto foo = container->resolve<IFoo>();
}

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