The project has a goal: use interface centric design. Basically we declare classes of only public pure functions and have real classes inherit from them.
One question comes up now – how to reuse the utility functions?
Here is an example:
class InterF1 {
public:
int f1() = 0;
int g1() = 0;
};
class InterF2 {
public:
int f2() = 0;
};
class C1: public InterF1 {
public:
int f1();
int g1();
};
class C2: public InterF1, InterF2 {
public:
int f1();
int g1();
int f2();
};
While I am implementing C1::f1() and C2::f1(), I see code duplication between these 2 functions. How should I remove the duplication?
Here are my thoughts:
1) I can add protected to interface class like this:
class InterF1 {
public:
int f1() = 0;
int g1() = 0;
protected:
int util();
int convenient_var_calculated_by_util;
};
in the above design, C1::f1() and C2::f2() can both call util().
2) I can abstract a new interface:
class UtilI {
public:
int util(int in_var) = 0;
};
In this design, InterF1, C1 and C2 are changed to
class InterF1 {
public:
int f1(UtilI u) = 0;
int g1() = 0;
};
class C1: public InterF1 {
public:
int f1(UtilI u);
int g1();
};
class C2: public InterF1, InterF2 {
public:
int f1(UtilI u);
int g1();
int f2();
};
and C1::f1() and C2::f1() both call UtilI API.
Solution 2 seems more aligned with our “interface centric” goal and it does look better. But I have one concern, InterF1 and InterF2 themselves should be higher level abstraction than UtilI which is more of implementation detail, am I mixing the 2 levels? Another words, if implementation changes later, I will need to come back to update f1() and f2() signature again, that does not sound right.
Solution 1 does seem handy, but it looks less “pure” to me, am I too much dogmatic here?
Normally I would say you should put the common implementation in the base class – but you have a requirement that the base class is purely virtual. So instead I’d recommend either:
Create an intermediate implementation class
This approach is okay if every class that wants to implement
InterF2also wants to implementInterF1.The approach is to implement
Then
C1andC2can both be derived fromInterF1Implwhich implements the commonf1()andg1()methods.However, this approach won’t extend well if you have another class,
C3 : public InterF2, which wants to share an implementation off2()withC2but doesn’t want to implementInterF1.An alternative (and better) approach is
Use composition to include an implementation class.
In this approach, implement
InterF1Impl: public InterF1as above, but instead of derivingC1: public InterF1Impl, letInterF1Implbe one part of theC1class.The
C1::f1()andC1::g1()methods simply call the corresponding methods fromf1impl.This way, if you need a common implementation of
f2(), then you can also implementInterF2: public InterF2, andC2can be implemented in a similar way:And another class could just use the implementation of
InterF2without also implementingInterF1if you later needed such a class.