I came across this problem via a colleague today. He had a design for a front end system which goes like this:
class LWindow
{
//Interface for common methods to Windows
};
class LListBox : public LWindow
{
//Do not override methods in LWindow.
//Interface for List specific stuff
}
class LComboBox : public LWindow{} //So on
The Window system should work on multiple platforms. Suppose for the moment we target Windows and Linux. For Windows we have an implementation for the interface in LWindow
. And we have multiple implementations for all the LListBox
es, LComboBox
es, etc. My reaction was to pass an LWindow*
(Implementation object) to the base LWindow
class so it can do this:
void LWindow::Move(int x, int y)
{
p_Impl->Move(x, y); //Impl is an LWindow*
}
And, do the same thing for implementation of LListBox
and so on
The solution originally given was much different. It boiled down to this:
#define WindowsCommonImpl {//Set of overrides for LWindow methods}
class WinListBox : public LListBox
{
WindowsCommonImpl //The overrides for methods in LWindow will get pasted here.
//LListBox overrides
}
//So on
Now, having read all about macros being evil and good design practices, I immediately was against this scheme. After all, it is code duplication in disguise. But I couldn't convince my colleague of that. And I was surprised that that was the case. So, I pose this question to you. What are the possible problems of the latter method? I'd like practical answers please. I need to convince someone who is very practical (and used to doing this sort of stuff. He mentioned that there's lots of macros in MFC!) that this is bad (and myself). Not teach him aesthetics. Further, is there anything wrong with what I proposed? If so, how do I improve it? Thanks.
EDIT: Please give me some reasons so I can feel good about myself supporting oop :(
Going for bounty. Please ask if you need any clarifications. I want to know arguments for and vs OOP against the macro :)
LListBox and LComboBox should receive an instance of WindowsCommonImpl.
In the first solution, inheritance is used so that LListBox and LComboBox can use some common methods. However, inheritance is not meant for this.
OP seems confused. Here' what to do, it is very complex but it works.
Rule 1: Design the abstractions. If you have an "is-A" relation you must use public virtual inheritance.
Rule 2: Make implementations, if you're implementing an abstraction you must use virtual inheritance. You are free to use inheritance to save on duplication.
Therefore you should read "virtual" to mean "isa" and other inheritance is just saving on rewriting methods.
Rule3: Try to make sure there is only one useful function in a concrete type: the constructor. This is sometimes hard, you may need some default and some set methods to fiddle things. Once the object is set up cast away the implementation. Ideally you'd do this on construction:
Notes: you are not going to call any abstract methods directly on or in an implementation so private inheritance of abstract base is just fine. Your task is exclusively to define these methods, not to use them: that's for the clients of the abstractions only. Implementations of virtual methods from the bases also might just as well be private for the same reason. Inheritance for reuse will probably be public since you might want to use these methods in the derived class or from outside of it after construction to configure your object before casting away the implementation details.
Rule 4: There is a standard implementation for many abstractions, known as delegation which is one you were talking about:
This is a cute implementation since it doesn't require you to know anything about the abstraction or how to implement it... :)
I found that
[src]
Macros are apparently not as precise, I did not even know that... The classic hidden dangers of the preprocessor like:
[src]
Your colleague is probably thinking of the MFC message map macros; these are used in important-looking places in every MFC derived class, so I can see where your colleague is coming from. However these are not for implementing interfaces, but rather for details with interacting with the rest of the Windows OS.
Specifically, these macros implement part of Windows' message pump system, where "messages" representing requests for MFC classes to do stuff gets directed to the correct handler functions (e.g. mapping the messages to the handlers). If you have access to visual studio, you'll see that these macros wrap the message map entries in a somewhat-complicated array of structs (that the calling OS code knows how to read), and provide functions to access this map.
As MFC users, the macro system makes this look clean to us. But this works mostly because underlying Windows API is well-specified and won't change much, and most of the macro code is generated by the IDE to avoid typos. If you need to implement something that involves messy declarations then macros might make sense, but so far this doesn't seem to be the case.
Practical concerns that your colleague may be interested in:
So basically your LWindow solution is a better solution, to minimize headaches down the road.
Oh man this is confusing.
OK, so L*** is a hierarchy of interfaces, that's fine. Now what are you using the p_Impl for, if you have an interface, why would you include implementation in it?
The macro stuff is of course ugly, plus it's usually impossible to do. The whole point is that you will have different implementations, if you don't, then why create several classes in the first place?
I would agree with you. Solution with
WindowsCommonImpl
macro is really bad. It is error-prone, hard to extend and very hard to debug. MFC is a good example of how you should not design your windows library. If it looks like MFC, you are really on a wrong way.So, your solution obviously better than macro-based one. Anyway, I wouldn't agree it is good enough. The most significant drawback to me is that you mix interface and implementation. Most practical value of separating interface and implementation is ability to easily write mock objects for testing purposes.
Anyway, it seems the problem you are trying to solve is how to combine interface inheritance with implementation inheritance in C++. I would suggest using template class for window implementation.
As I remember WTL windows library is based on the similar pattern of combining interfaces and implementations. I hope it helps.