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

Inheritance in components #19

Closed
Myushu opened this issue Jun 9, 2016 · 5 comments
Closed

Inheritance in components #19

Myushu opened this issue Jun 9, 2016 · 5 comments

Comments

@Myushu
Copy link

Myushu commented Jun 9, 2016

Hi,

At times we have sub-classes of our components. Is it possible to "pull" the installs from another component? Example:

struct InterfaceParent {};
struct ImplParent : InterfaceParent {};

fruit::Component<InterfaceParent> getParent() {
 return fruit::createComponent()
    .install(...)
    .install(...)
    .install(...)
   .bind<InterfaceParent, ImplParent>();
}

struct InterfaceChild : InterfaceParent {};
struct ImplChild : InterfaceChild {};
fruit::Component<InterfaceChild> getChild() {
 return fruit::createComponent()
   .useInstallsFrom(getParent()) // ??? is it possible to do something like this?
  .bind<InterfaceChild, ImplChild>();
}
@poletti-marco
Copy link
Contributor

poletti-marco commented Jun 9, 2016

Could you please provide more information on your use case? Let's start with:

  • Is this production code, or are part of it test code? If so, which ones?
  • Is ImplParent an abstract class? (i.e., does it have any non-implemented virtual methods?)
  • Is ImplParent only used as a base class for ImplChild (and possibly other derived classes), or are there any other places that use ImplParent?
  • Is deriving from ImplParent an implementation detail of ImplChild, or are users of ImplChild allowed to know (and depend on the fact) that ImplChild is an ImplParent?
  • If you drew a UML component diagram including {Interface,Impl}{Child,Parent} and the components directly using those, how would it look like? (note that multiple classes/interfaces might be in the same UML component, e.g. if ImplParent is only an implementation detail of ImplClient.) The relationships between Fruit components are closely tied to how your component diagram looks like.

@vendethiel
Copy link

Is this production code, or are part of it test code? If so, which ones?

Production code. What's missing, however, is the diamond. It technically looks like this.

struct InterfaceParent {};
struct ImplParent : virtual InterfaceParent {};

fruit::Component<InterfaceParent> getParent() {
 return fruit::createComponent()
    .install(...)
    .install(...)
    .install(...)
   .bind<InterfaceParent, ImplParent>();
}

struct InterfaceChild : virtual InterfaceParent {};
struct ImplChild : InterfaceChild, ImplParent {};
fruit::Component<InterfaceChild> getChild() {
 return fruit::createComponent()
   .useInstallsFrom(getParent()) // ??? is it possible to do something like this?
  .bind<InterfaceChild, ImplChild>();
}

Is ImplParent an abstract class? (i.e., does it have any non-implemented virtual methods?)

No, the parent is standalone, and can itself be injected. In this case, it's ImplParent that's used.

Is ImplParent only used as a base class for ImplChild (and possibly other derived classes), or are there any other places that use ImplParent?

Is deriving from ImplParent an implementation detail of ImplChild, or are users of ImplChild allowed to know (and depend on the fact) that ImplChild is an ImplParent?

Only used there. It's supposedly an implementation detail.

If you drew a UML component diagram

I've never done UML, sorry.


To summarize a bit on the issue, I'll take a real-world use case.

Let's say I have a "BaseModel" interface. It has a "BaseModelImpl". (Which it binds to. The impl is obviously an implementation detail). That's for the library part.

Now, I want to extend it in my application, and I'd like to have a TimestampableModel, which has a method touch on it, that updates the timestamps. The Impl class is TimestampableModelImpl.

Since we don't want to redefine every single method that BaseModel exposes in TimestampableModelImpl (i.e. by injecting the BaseModel in TimestampableModelImpl, then redefining every method to forward to BaseModel), we directly inherit BaseModelImpl in TimestampableModelImpl. It's all an implement detail – whoever injects TimestampableModel (the interface) needn't care about how they're implemented.

@poletti-marco
Copy link
Contributor

Since the user of such a library (who writes TimestampableModelImpl) needs to have access to the definition of BaseModelImpl (not just to have access to a BaseModel object that happens to be a BaseModelImpl) BaseModelImpl must be exposed in the library's public interface, you can't just hide that definition in a cpp file as you'd normally do with *Impl classes when using Fruit.

You could do sth like:

// base_model.h
struct BaseModel {...};
fruit::Component<BaseModel> getBaseModelComponent();

// base_model_impl.h
// This is a *public* header, even though it has an _impl suffix!
#include "base_model.h"
struct Foo;
struct Bar;
struct BaseModelImpl : public BaseModel {
    INJECT(BaseModelImpl(Foo* foo, Bar* bar)) {...}
    ...
};
// Use this when extending BaseModel
fruit::Component<Foo, Bar> getBaseModelDepsComponent();

// base_model.cpp
#include "foo.h"
#include "bar.h"
#include "base_model_impl.cpp"
... // Implementation of various BaseModelImpl methods
fruit::Component<Foo, Bar> getBaseModelDepsComponent() {
    return fruit::createComponent()
        .install(getFooComponent())
        .install(getBarComponent());
}
fruit::Component<BaseModelImpl> getBaseModelComponent() {
    return fruit::createComponent()
        .install(getBaseModelDepsComponent())
        .bind<BaseModel, BaseModelImpl>();
}

// timestampable_model.h
#include "base_model.h"
struct TimestampableModel : public Model {
    virtual void touch() = 0;
};
fruit::Component<TimestampableModel> getTimestampableModelComponent();

// timestampable_model.cpp
#include "timestampable_model.h"
#include "base_model_impl.h"
#include "baz.h"
struct TimestampableModelImpl : public BaseModelImpl, public TimestampableModel {
    INJECT(TimestampableModel(Foo* foo, Bar* bar, Baz* baz))
        : BaseModelImpl(foo, bar) { ... }
    ....
};
fruit::Component<TimestampableModel> getTimestampableModelComponent() {
    return fruit::createComponent()
        .install(getBaseModelDepsComponent())
        .install(getBazComponent())
        .bind<TimestampableModel, TimestampableModelImpl>();
}

Does this structure achieve what you want?
If you want multiple levels (i.e., if you want to allow a user of the library that exposes TimestampableModel to also be able to inherit from TimestampableModelImpl), you can also have a getTimestampableModelDepsComponent() with a structure similar to the one for base_model, where you make TimestampableModelImpl part of your library's public interface.

@vendethiel
Copy link

Ugh, sorry. I thought we followed up on this. We went with your suggestion in the end, and it's worked well. You can close this issue.

@poletti-marco
Copy link
Contributor

Ok, happy to know that it worked for you. Closing this then.

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