Reputation: 627
I'm trying to implement a library where Class1 provides about five public methods Method1 to Method5. Class2 provides two methods - Methods6 and Method7. And Class3 provides one method - Method8. Now, for the end user, I want to expose methods from combination of these classes. E.g. If the end user instantiates a class called Class1Class2, they should have access to Method1 to Method7, if they instantiate a class called Class1Class3, they should have access to Method1 to Method5 and Method8.
There are 3 different approaches I could think of (please suggest any others as well):
Multiple inheritance: Keep each of Class1, Class2 and Class3 as it is. Then, create a new class Class1Class2 that publicly multiple inherits from Class1 and Class2. Similarly I can create a class Class1Class3 that publicly multiple inherits from Class1 and Class3.
Multi-level inheritance: I could derive Class2 from Class1, and call that Class1Class2. And Class3 from Class1 and call that Class1Class3. And if we need Class1Class2Class3, we inherit that class from Class2 and Class3, which have both derived from Class1. Here, we would use virtual inheritance to resolve the diamond problem. I don't expect to use Class2Class3, so that shouldn't be a problem here.
Composition: Keep each of Class1, Class2 and Class3 as it is. Create Class1Class2 that implements each of the methods Method1 to Method7 and internally delegate them to the objects of Class1 and Class2 accordingly. Similarly, a Class1Class3 would compose objects of Class1 and Class3. With this approach we need to provide implementations for all the methods and delegate them to the composed objects.
While "Composition over inheritance" guideline is generally great for loose coupling of classes, etc., in the above case, where we have to do code reuse from separate concrete implementations, Approach 1 or 2 seem like better options.
Upvotes: 0
Views: 117
Reputation: 13269
You are only talking about code reuse here. I would think that's because you don't actually need nor want actual polymorphism. If this is indeed the case, then consider private inheritance and using
to expose the parent methods.
For example:
class Class1Class2 : Class1, Class2 {
public:
using Class1::Method1;
// ...
using Class2::Method6;
// ...
};
Private inheritance, while technically being called inheritance, is very different from public inheritance, which itself is conceptually different from subtyping.
In C++ (and a lot of other languages that support OOP), public inheritance usually provides both subtyping and code reuse. However, it is entirely possible to derive a class with incorrect behavior in places that expect the parent class. This potentially undermines subtyping. It is also entirely possible to derive a class that does not reuse any of the implementation of the parent class. This potentially undermines code reuse.
Conceptually, subtyping is expressed by interface inheritance, while implementation inheritance is only one way to reuse code. The "composition over inheritance" saying, as far as I understand it, is about using other tools to reuse code because implementation inheritance often leads to bad code. However, there isn't really another way to achieve true subtyping than inheritance, so it may still be useful there1.
On the other hand, private inheritance is just an odd form of composition. It simply replaces the member with a private base class. An advantage of this is the ability to use using
to easily expose the parts of that "member" you want to expose.
1 I personally don't like either forms of (public) inheritance, prefering static polymorphism and compile-time duck typing. However, I can happily work with interface inheritance, whereas I usually stay far away from implementation inheritance.
Upvotes: 2
Reputation: 217810
As you want easy combination, you might use template as variant of your first proposal:
template <typename ... Bases>
struct Derived : Bases...
{
};
using Class1Class2 = Derived<Class1, Class2>;
using Class1Class2Class3 = Derived<Class1, Class2, Class3>;
Upvotes: 1
Reputation: 6584
To make things more interesting you may employ CRTP. Here is an example:
template<typename Base>
class ClassA {
public:
void MethodA1() {static_cast<Base*>(this)->MethodA1_Impl();}
void MethodA2() {static_cast<Base*>(this)->MethodA2_Impl();}
};
template<typename Base>
class ClassB {
public:
void MethodB1() {static_cast<Base*>(this)->MethodB1_Impl();}
void MethodB2() {static_cast<Base*>(this)->MethodB2_Impl();}
};
template<typename Base>
class ClassC {
public:
void MethodC1() {static_cast<Base*>(this)->MethodC1_Impl();}
void MethodC2() {static_cast<Base*>(this)->MethodC2_Impl();}
};
class ClassABC: public ClassA<ClassABC>, public ClassB<ClassABC>, public ClassC<ClassABC> {
public:
//void MethodA1_Impl();
//void MethodA2_Impl();
//void MethodB1_Impl();
//void MethodB2_Impl();
//void MethodC1_Impl();
//void MethodC2_Impl();
};
You may uncomment and implement ANY subset of MethodXY_Impl()
, and that would compile. The client code may call any method from MethodXY()
. If there is no corresponding implementation - the compiler would produce an error.
Upvotes: 1