google / fruit

Fruit, a dependency injection framework for C++
https://github.com/google/fruit/wiki
Apache License 2.0
1.81k stars 200 forks source link

Using factory parameter in a nested class #130

Open Simmay93 opened 3 years ago

Simmay93 commented 3 years ago

Hi there,

I have the following problem: I have a class with an assisted parameter and a component in the constructor. The assisted variable is needed for the construction of the component. How can I solve this problem. I did not find a straight forward solution in the wiki.

class OuterClass {
  public INJECT(OuterClass(ASSISTED(const TypeEnum) type, IClass* innerClass));
};

for the innerClass I have a method which returns an actual class according to the type:

fruit::Component<IClass> getInnerClassComponent(const TypeEnum type)
{
  switch (type)
  {
    case TypeEnum::A: return getType1Component();
    // etc
}

the factory looks like this:

fruit::Component<OuterClassFactory> getOuterClassComponent()
{
  return fruit::createComponent().bind<IOuterClass, OuterClass>().install(getInnerClassComponent, TypeEnum::A); // <-- here I want to use the param with which the factory is called
}
tt4g commented 3 years ago

If InnterClass is determined by TypeEnum, it should be provided in a way other than injecting it. You can use registerFactory()`` to generate theInnerClassfrom theTypeEnum` at the time of injection.

Example:

fruit::Component<std::function<std::unique_ptr<IOuterClass>(TypeEnum)> getOuterClassComponent()
{
  return fruit::createComponent()
        .bind<IOuterClass, OuterClass>()
        .registerFactory<std::unique_ptr<OuterClass>(TypeEnum)>(
           [](TypeEnum type) {
               IClass* innerClass = getInnerClassComponent(type);

               return std::make_unique<OuterClass>(type, innerClass);
           });
}

// Usage

std::function<std::unique_ptr<IOuterClass>(TypeEnum)> iOuterClassFactory(injector);
std::unique_ptr<IOuterClass> outerClass = iOuterClassFactory(TypeEnum::A);

Look "Factories and assisted injection" section: https://github.com/google/fruit/wiki/quick-reference#factories-and-assisted-injection

Simmay93 commented 3 years ago

That looks pretty nice. Thanks for the fast answer.

I have one more question: The function getInnerClassComponent() is also a fruit::Component. How can I create the instance from this or do I have to implement it without fruit?

tt4g commented 3 years ago

If IClass is allowed to create a different instance each time the factory function is called (not a singleton), then getInnerClassComponent() should be a factory fruit component. You should be able to get that factory function by using fruit::Assisted in getOuterClassComponent().

Example:

fruit::Component<std::function<std::unique_ptr<IOuterClass>(TypeEnum)> getOuterClassComponent()
{
  return fruit::createComponent()
        .bind<IOuterClass, OuterClass>()
        .registerFactory<
            std::unique_ptr<OuterClass>(
                TypeEnum,
                fruit::Assisted<std::function<std::unique_ptr<IClass>(TypeEnum)>>&)>(
                    [](TypeEnum type, 
                       std::function<std::unique_ptr<IClass>(TypeEnum)>& innerClassFactory) {
                        std::unique_ptr<IClass> innerClass = getInnerClassComponent(type);

                        return std::make_unique<OuterClass>(type, std::move(innerClass));
                    });
}

If you want to make IClass a singleton, you can use Annotated Injection to add a mark to identify the instance of IClass to fruit. Wiki: https://github.com/google/fruit/wiki/quick-reference#annotated-injection

poletti-marco commented 3 years ago

+1 to the suggestion to use annotated injection, I think that's a good fit here. You might want to use an annotation type templated on the enum:

enum TypeEnum { ... };

template <TypeEnum X>
struct TypeEnumAnnotation {};

And then using fruit::Annotated<TypeEnumAnnotation<SOME_VALUE>, IClass>.

If you want an OuterClass instance for each enum value, you could templatize that on TypeEnum too (templatizing getOuterClassComponent too to match).

Assisted injection is more for cases where you want to supply the enum value outside of Fruit, after injection; while here it seems that you know the value from the get*Component stage already so you can pass it there and Fruit can handle the IClass instances as singletons for you (1 per enum value).

P.S. @tt4g : thanks for replying, very appreciated (as always)!

Simmay93 commented 3 years ago

Thank you both for your suggestions but hardly I still don't get it.

Maybe I explain my code a little bit more.

OuterClass is constructed several times during runtime. The concrete implementations of IClass can be singletons. The type of the singleton is only known at construction time.

As far as I got the documentation and your help I need a factory for OuterClass. That factory needs the type due to the construction time constraint. But inside the factory function I have to create the concrete class without any fruit "magic". How can I connect this to the singleton with the correct type?

poletti-marco commented 3 years ago

OuterClass is constructed several times during runtime.

Is this 1 per value of the enum, or are there enum values for which you want to construct multiple instances of that with the same enum value? Or is it ok either way?

Is there a single enum value that you use (and it's just determined later) or do you want to comstruct OuterClass instances with different enum values from the same injector?

The type of the singleton is only known at construction time.

Can you expand on this? Is that determined from the enum value alone or also other data?

Simmay93 commented 3 years ago

OuterClass is constructed several times with several enum values (n-m relation). There are 3 enum values. So there are 3 different "types" of IClass. These can implemented as singleton using fruit::Component.

I'd like to construct OuterClass instances from the same injector (if possible). The enum value is known at construction time of the OuterClass implementation. It is just determined from the enum value. It is given into the constructor and depending on the enum value we need the specific "type" of IClass.

Thats why I did the approach with the ASSISTED argument and the factory approach but I miss the point how to bring this together with the fruit::Component of IClass

tt4g commented 3 years ago

@Simmay93 I create a small project: https://github.com/tt4g/fruit-issue-130/tree/main This project creates a singleton instance of IClass and OuterClass for each TypeEnum.

It may help you solve your problem by browsing the source.