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

Using factory parameter in a nested class #130

Open
Simmay93 opened this issue Feb 9, 2021 · 8 comments
Open

Using factory parameter in a nested class #130

Simmay93 opened this issue Feb 9, 2021 · 8 comments

Comments

@Simmay93
Copy link

Simmay93 commented Feb 9, 2021

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

tt4g commented Feb 9, 2021

If InnterClass is determined by TypeEnum, it should be provided in a way other than injecting it.
You can use registerFactory()`` to generate the InnerClassfrom 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
Copy link
Author

Simmay93 commented Feb 9, 2021

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

tt4g commented Feb 9, 2021

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

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

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

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

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

tt4g commented Feb 13, 2021

@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.

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

3 participants