Git Product home page Git Product logo

Comments (5)

poletti-marco avatar poletti-marco commented on May 1, 2024

I think that this use case can be solved using annotated injection.

Option 1: Producer is not cached by default

In this case you could write something like:

// In some header, say producer.h.
fruit::Component<Producer> getProducerComponent();
// In the file with ProducerImpl.
fruit::Component<Producer> getProducerComponent() {
    return fruit::createComponent()
        .bind<Producer, ProducerImpl>();
}
// In cached_producer.h
// This is just a marker so that Fruit can distinguish between the "cached Producer"
// and the "plain Producer".
struct Cached {};

fruit::Component<fruit::Annotated<Cached, Producer>> getCachedProducerComponent();
// In cached_producer.cpp
fruit::Component<fruit::Annotated<Cached, Producer>> getCachedProducerComponent() {
    return fruit::createComponent()
        .install(getProducerComponent)
        .bind<fruit::Annotated<Cached, Producer>, ProducerImpl>();
}

When you inject a Producer, you'll get the non-cached version; if you want the cached one you have to specify the annotation too. E.g.:

// In bar.cpp
struct Bar {
    INJECT(Bar(ANNOTATED(Cached, std::shared_ptr<Producer>) producer)) { ...}
};

Option 2: Producer is cached by default

If most of your code needs the cached version, you might want to annotate the non-cached one instead:

// In some header, say producer.h.
struct NotCached {};
fruit::Component<fruit::Annotated<NotCached, Producer>> getNotCachedProducerComponent();
// In the file with ProducerImpl.
fruit::Component<fruit::Annotated<NotCached, Producer>> getNotCachedProducerComponent() {
    return fruit::createComponent()
        .bind<fruit::Annotated<NotCached, Producer>, ProducerImpl>();
}
// In cached_producer.h    
fruit::Component<fruit::Annotated<Cached, Producer>> getProducerComponent();
// In cached_producer.cpp
struct CachingProducerImpl : ProducerImpl {
    INJECT(CachingProducerImpl(
        ANNOTATED(NotCached, std::shared_ptr<Producer>) producer)) {...}
};
fruit::Component<Producer> getProducerComponent() {
    return fruit::createComponent()
        .install(getNotCachedProducerComponent)
        .bind<Producer, ProducerImpl>();
}

When you inject a Producer, you'll get the cached version. E.g.:

// In bar.cpp
struct Bar {
    INJECT(Bar(std::shared_ptr<Producer> producer)) { ...}
};

Hope that helps.

from fruit.

SimonEbner avatar SimonEbner commented on May 1, 2024

Awesome, that is a nice and straightforward solution. I should have thought of that. Thank you very much for the code samples, I highly appreciate. I would have two follow-up question regarding the annotated approach if I may:

  1. Can I have multiple annotations?
  2. Is it possible to specialize a generic component using annotations?

Multiple annotations

Let's say I want to have two cached component, can I do something like this:

Component<Annotated<StreamA, Cached, Provider>> getStreamAComponent();
Component<Annotated<StreamB, Cached, Provider>> getStreamBComponent();

Specialize a generic component using annotations

Can I have a generic component and specialize (not sure if that is the right word) it using an annotation in the install-step? In code that would look something like this:

// provider_impl.h
Component<Required<Configuration>, Provider> getProviderComponent();
// stream_a_provider_impl.h
struct StreamA {};
Component<Configuration> getStreamAConfiguration();
Component<Annotated<StreamA, Provider>> getStreamAProviderComponent();
// stream_a_provider_impl.cpp
Component<Configuration> getStreamAConfiguration() {...};
Component<Annotated<StreamA, Provider>> getStreamAProviderComponent() {
   return createComponent()
      .install(getStreamAConfiguration)
      .install<Annotated<StreamA>>(getProviderComponent); // illegal pseudo-code

The real use case is that I want to enable the provider to accept a configuration injection. So depending if I use StreamA or StreamB, I want to inject a different configuration while the provider implementation can remain generic accepting different configurations. So in the StreamA implementation I just want to wire together configuration and generic implementation and put it into an annotated component.

from fruit.

poletti-marco avatar poletti-marco commented on May 1, 2024

Multiple annotations? No, but...

Technically you can't have multiple annotations. However, the annotation type can be any type, including templated types. So you can define a template like:

template <typename StreamType, typename... OtherAnnotations>
struct StreamAnnotation {};

And then use fruit::Annotated<StreamAnnotation<StreamA, Cached>, Provider>.

Since StreamAnnotation<StreamA, Cached> and StreamAnnotation<StreamB, Cached> are different types, Fruit will consider the bindings fruit::Annotated<StreamAnnotation<StreamA, Cached>, Provider> and fruit::Annotated<StreamAnnotation<StreamB, Cached>, Provider> as different bindings (as you want).

In fact, you could just use std::tuple<> to group types (although IMO a custom template defined for this purpose as above would be more clear):

fruit::Annotated<std::tuple<StreamA, Cached>, Producer>

There are a couple caveats with this though.

  • If you have "multiple annotations", order matters. E.g. fruit::Annotated<std::tuple<StreamA, Cached>, Producer> and fruit::Annotated<std::tuple<Cached, StreamA>, Producer> are considered different, so you can't bind one and use the other.
  • If you use ANNOTATED() in a constructor, the first argument can't contain a comma, so you might have to define the templated type using a typedef and then use the typedef in ANNOTATED().

Specializing a component? Yes, but...

Component functions can be templated. So you can write something like:

// In producer.h
struct ProducerImpl : Producer {
    // Note: no INJECT here, we're using an explicit registerConstructor below
    ProducerImpl(Configuration configuration) {...}
};    

template <typename Annotation>
fruit::Component<
    Required<fruit::Annotated<Annotation, Configuration>>, 
    fruit::Annotated<Annotation, Producer>> 
getProducerComponent() {
    return fruit::createComponent()
        .registerConstructor<fruit::Annotated<Annotation, ProducerImpl>(
            fruit::Annotated<Annotation, Configuration>)>()
        .bind<fruit::Annotated<Annotation, Producer>, fruit::Annotated<Annotation, ProducerImpl>>();
}
// stream_a_producer_impl.cpp
Component<Annotated<StreamA, Configuration>> getStreamAConfiguration() {...};
Component<Annotated<StreamA, Producer>> getStreamAProducerComponent() {
     return createComponent()
          .install(getStreamAConfiguration)
          .install(getProducerComponent<StreamA>);
}

However, since getProducerComponent becomes a template, you'll need to implement that in provider.h instead of in a cpp file, and since the implementation needs ProducerImpl, you need to define that in provider.h too (the methods can still be implemented in a separate cpp file, but the class definition needs to be there so that Fruit can find the constructor).

So this makes getProducerComponent more coupled with getStream*ProducerComponent.
It may or may not be an issue, depending on your use case. E.g. if there are just 2 stream classes, defined in the same folder, this is not too bad; but if you have 20 scattered all over your codebase it's probably an issue.

Factories

Note: this is a bit less efficient (it will end up doing 1 additional new for each stream type you inject) and quite a bit more complex; so if the solution above already works well for you, I would advise against doing this.

For more information, see the section on factories and assisted injection in the reference documentation.

// In producer.h
fruit::Component<std::function<std::unique_ptr<Producer>(Configuration)>>
getProducerFactoryComponent();

// Optional, only if you also want to use the non-cached producer somewhere other
// than CachedProducerImpl.
template <typename Annotation>
fruit::Component<
    Required<fruit::Annotated<Annotation, Configuration>>, 
    fruit::Annotated<Annotation, Producer>> 
getProducerComponent() {
    return fruit::createComponent()
        .install(getProducerFactoryComponent)
        .registerProvider<fruit::Annotated<Annotation, ProducerImpl(
            std::function<std::unique_ptr<Producer>(Configuration)>,
            fruit::Annotated<Annotation, Configuration>)>(
                [](std::function<std::unique_ptr<Producer>(Configuration)>& producerFactory,
                    Configuration configuration) {
                    return producerFactory(configuration).release();
            });
        .bind<Producer, ProducerImpl>();
}
// In producer.cpp
fruit::Component<std::function<std::unique_ptr<Producer>(Configuration)>>
getProducerComponent() {
    return fruit::createComponent()
        .bind<Producer, ProducerImpl>();
}
// In cached_producer.h
fruit::Component<fruit::Annotated<
    Cached,
    std::function<std::unique_ptr<Producer>(Configuration)>>>
getCachedProducerFactoryComponent();

template <typename StreamType>
fruit::Component<
    Required<fruit::Annotated<StreamType, Configuration>>, 
    fruit::Annotated<StreamAnnotation<StreamType, Cached>, Producer>> 
getCachedProducerComponent() {
    return fruit::createComponent()
        .install(getCachedProducerFactoryComponent)
        .registerProvider<fruit::Annotated<StreamAnnotation<StreamType, Cached>, ProducerImpl>(
            fruit::Annotated<Cached, std::function<std::unique_ptr<Producer>(Configuration)>,
            fruit::Annotated<Annotation, Configuration>)>(
                [](std::function<std::unique_ptr<Provider>(Configuration)>& providerFactory,
                    Configuration configuration) {
                    return providerFactory(configuration).release();
            });
        .bind<
            fruit::Annotated<StreamAnnotation<StreamType, Cached>, Producer,
            fruit::Annotated<StreamAnnotation<StreamType, Cached>, ProducerImpl>();
}
// stream_a_provider_impl.cpp
Component<Annotated<StreamA, Configuration>> getStreamAConfiguration() {...};
Component<
    Annotated<StreamAnnotation<StreamA, Cached>, Provider>> 
getStreamAProviderComponent() {
     return createComponent()
          .install(getStreamAConfiguration)
          .install(getCachedProducerComponent<StreamA>);
}
// foo.cpp
struct FooImpl : Foo {
    // A typedef is needed, you can't inline this inside ANNOTATED because it contains a comma.
    using MyAnnot = StreamAnnotation<StreamA, Cached>;
    INJECT(Foo(ANNOTATED(MyAnnot, Provider) provider)) {...}
};

fruit::Component<Foo> getFooComponent() {
    return fruit::createComponent()
        .install(getStreamAProviderComponent)
        bind<Foo, FooImpl>();
}

from fruit.

poletti-marco avatar poletti-marco commented on May 1, 2024

Sorry, in the last part i missed a few pieces:

// In producer.cpp
struct ProducerImpl : Producer {
    ProducerImpl(ASSISTED(Configuration) configuration) {...}
};    
// In cached_producer.cpp    
struct CachingProducerImpl : ProducerImpl {
    std::unique_ptr<Producer> producer;
    INJECT(CachingProducerImpl(
        std::function<std::unique_ptr<Producer>(Configuration)>> producerFactory,
        ASSISTED(Configuration) configuration)
        : producer(producerFactory(configuration)) {
    }
};

fruit::Component<fruit::Annotated<
    Cached,
    std::function<std::unique_ptr<Producer>(Configuration)>>>
getCachedProducerFactoryComponent() {
    return fruit::createComponent()
        .install(getProducerFactoryComponent)
        .bind<Annotated<Cached, Producer>, CachingProducerImpl>();
}

from fruit.

SimonEbner avatar SimonEbner commented on May 1, 2024

Oh wow, that escalated quickly. I think sadly for my use case that is a bit too much boilerplate code. Probably I will sacrifice some flexibility for the sake of simplicity. But thank you very much for your explanation and pieces of code, I truly learnt a lot from this thread.

from fruit.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.