I have struggled with this design problem for some time. I will do my best to explain what I am trying to do and the various approached that I have seen, what I am trying and why.
I work in a scientific computing environment where I deal with the same kinds of objects repeatedly. Imagine a galaxy which contains solar systems, each solar system contains planetary systems and each planetary system contains moons. To this end I think of the situation as a “has a” situation, and thus I have used composition to give the galaxy access to its solar systems, and each solar system access to the planetary systems which have access to their moons: each category being its own class.
It is often the case that the various problems I am working on contain different types of data about these objects. And, as different types of data become available I can do certain things with my objects. So, when I have data type 1 available to me I create the following classes
class GalaxyOne { /* … */ };
class SolarSystemOne { /* … */ };
class PlanetOne{ /* … */ };
class MoonOne{ /* … */ };
And when I have data type 2 available to me I create
class GalaxyTwo { /* … */ };
class SolarSystemTwo { /* … */ };
class PlanetTwo{ /* … */ };
class MoonTwo{ /* … */ };
The composite aspects of each class are dealt with by using container variables such as vectors which contain pointers to the contained classes. For example, within each Galaxy class one would find.
class GalaxyTwo{ /* … */
protected:
std::vector<SolarSystemTwo*> solarSystems;
/* … */
};
The difficulty comes when I want to combine classes into higher-order classes.
class GalaxyOneTwo: public GalaxyOne, public GalaxyTwo{
/* Additional methods */
};
However, this creates a problem of ambiguity in the solarSystems vector because GalaxyOneTwo would have a version of it from GalaxyOne and GalaxyTwo. Furthermore, the vectors it inherits contain pointers to objects which are not of type SolarSystemOneTwo, which would be required. So, I thought I could create a template class which all of my objects inherit from where I put all of my container variables.
template<MoonT,PlanetT,SolarSystemT>
class PrimitiveGalaxy {
private:
std::vector<SolarSystemT*> solarSystems
};
class GalaxyOne: public PrimitiveGalaxy <MoonOne,PlanetOne,SolarSystemOne>{
/* No longer defining any container variables for composition */
};
This approach works very nicely for those fundamental Galaxy types (GalaxyOne and GalaxyTwo). However, whenever I try to create the combined galaxy type I get all kinds of ambiguity.
class GalaxyOneTwo: public GalaxyOne, public GalaxyTwo, public PrimitiveGalaxy<MoonOneTwo,PlanetOneTwo,SolarSystemOneTwo>{
/* … */
};
I get ambiguity if I use solarSystems within any method defined in GalaxyOneTwo because it is defined three times, once through each inherited version from GalaxyOne and GalaxyTwo and a third time through GalaxyOneTwo.
I can get rid of this ambiguity by being specific and using
PrimitiveGalaxy::solarSystems
each time to refer to the proper vector, but this is not desirable because it requires a LOT of extra typing and syntax.
I have considered using friendship between each galaxy type and the primitive galaxy but this requires a similar level of verbose writing.
I have a hunch that namespaces may simplify my writing of code but I am not sure how to define the namespace such that within a namespace defined for GalaxyOneTwo that any reference to solarSystems is a reference to PrimitiveGalaxy::solarSystems
Edit:
Please note, the only difference between GalaxyOne and GalaxyTwo is NOT the type of the class contained in solarSystems. There are many differences because each class deals with different data relevant to the galaxy. Thus, I create different classes which will have different state variables, getters and setters for those state variables and methods to calculate and print data. solarSystems is an example of the feature which is giving me problems thus that is what I have described here. When GalaxyOneTwo is created it will use the same data that is used for GalaxyOne and for GalaxyTwo and thus I want to inherit all of their variables and methods. And because the data can be combined in different ways I need to create new methods for that within GalaxyOneTwo. These are some of the many differences that lead me to use inheritance. That being said, it is the container variables that allow for composition that give me problems. Within each solarSystem class there will be a similar vector giving them access to their planets, and so on and so forth.
Edit:
For a question specifically dedicated to my design philosophy here in general (as opposed to this questions emphasis on trying to resolve my current design attempt) see the following link: Guidance in creating design for multiple-inheritance composite classes in c++
I think you have to have one
class Galaxy
, oneclass SolarSystem
, etc. And yoursGalaxyOne
,GalaxyTwo
SolarSystemOne
,SolarSystemTwo
etc. are only different objects instantited from these classes.....
If we have no way to use this simple aproach... Lets see yours:
Here you have three private vectors: (using
using
you make it direct accesible)Is this what you need? Make it protected?
You need to abstract out the common stuff between the 'One' and 'Two' versions of stuff into a set of 'Common' base classes:
Note the use of
virtual
-- required to get properly working multiple inheritance.Maybe you could change data models to implement something component based. Each component could contain the same state information you mentioned for the different types. And you could inherit virtual functions
You could then inherit the different types of solar systems or galaxies to create the special cases and fill out the virtual function calls
You could then populate the containers inside the different base types with the pointers to your special types. You should be able to avoid a cast back to the special type if the virtual function calls handle enough of that information.
So if I understand your question right:
This sounds like a case for boost::any
So now your Galaxy is:
Ofcourse, this doesn't really do anything, but does allow you to group them. In order to do anything useful (call a method) you will still have to detect the type, cast it to that type and then run the code you need to.
However, what this does allow you to do is to group objects of different types together, which I think is what you're after with GalaxyOneTwo. Here is more information on how you can do that.