Solving design involving multiple inheritance and

2019-03-20 06:38发布

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++

4条回答
女痞
2楼-- · 2019-03-20 07:20

I think you have to have one class Galaxy, one class SolarSystem, etc. And yours GalaxyOne, GalaxyTwo SolarSystemOne, SolarSystemTwo etc. are only different objects instantited from these classes.

class SolarSystem { /* … */ };
class Planet{ /* … */ };
class Moon{ /* … */ };

class Galaxy{ /* … */
 public: // Galaxy(...?...){for (...) {read data??; solarSystem.push_back(createSolarSystem(data)); }
 void AddSolarSystem(SolarSystem* ss){solarSystem.push_back(ss);}
 protected:
   std::vector<SolarSystem*> solarSystems;
   /* … */
};

....

Galaxy GalaxyOne, GalaxyTwo;

If we have no way to use this simple aproach... Lets see yours:

class GalaxyOneTwo: public GalaxyOne, 
                    public GalaxyTwo, 
                    public PrimitiveGalaxy<MoonOneTwo,PlanetOneTwo,SolarSystemOneTwo>{
    /* … */
 using PrimitiveGalaxy<MoonOneTwo,PlanetOneTwo,SolarSystemOneTwo>::solarSystems;
 GalaxyOneTwo(){solarSystems.reserve(10);}
};

Here you have three private vectors: (using using you make it direct accesible)

std::vector<SolarSystemOne*   > GalaxyOne::solarSystems;
std::vector<SolarSystemTwo*   > GalaxyTwo::solarSystems;
std::vector<SolarSystemOneTwo*> solarSystems; //GalaxyOneTwo::solarSystems;

Is this what you need? Make it protected? enter image description here

查看更多
兄弟一词,经得起流年.
3楼-- · 2019-03-20 07:28

You need to abstract out the common stuff between the 'One' and 'Two' versions of stuff into a set of 'Common' base classes:

class GalaxyCommon {
    // all the common stuff between GalaxyOne and GalaxyTwo including
    std::vector<SolarSystemCommon *> solarSystems;
    ...
};

class GalaxyOne : public virtual GalaxyCommon {
    // all the stuff that is unique to model One
};

class GalaxyTwo : public virtual GalaxyCommon {
    // all the stuff that is unique to model Two
};

class GalaxyOneTwo : public virtual GalaxyOne, public virtual GalaxyTwo {
    // should be complete
};

Note the use of virtual -- required to get properly working multiple inheritance.

查看更多
叛逆
4楼-- · 2019-03-20 07:37

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

class Galaxy
{
  // galaxy information

  // Virtual functions shared among galaxies
  virtual void sharedGalaxyFunction() = 0;

  // Vector containing all solar systems in this galaxy
  std::vector<SolarSystem*> solarSystems_;
};

class SolarSystem
{     
  // solar system info

  // Virtual functions shared among all solar systems
  virtual void sharedSolarSystemFunction() = 0;

  // Vector containing planets
  std::vector<Planets*> planets_;
};

// etc for planets...

You could then inherit the different types of solar systems or galaxies to create the special cases and fill out the virtual function calls

class CoolSolarSystem : public SolarSystem
{
  // Special variables

  // Fill in the virtual function
  void sharedSolarSystemFunction();
};

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.

查看更多
Rolldiameter
5楼-- · 2019-03-20 07:40

So if I understand your question right:

  1. There are several types of galaxies that can have nothing at all in common between them.
  2. The same goes for Solar and planetary systems.
  3. You would like to combine collections of these different types and yet have full access to the underlying methods for that type.

This sounds like a case for boost::any

So now your Galaxy is:

#include <list>
#include <boost/any.hpp>
typedef std::vector<boost::any> collection;
class Galaxy
{
   collection SolarSystems;
   public:
   ...methods...
};
class SolarSystem
{
   collection PlanetarySystems;
   public:
   ...methods...
};
class PlanetarySystem
{
   collection Moons;
   public:
   ...methods...
};

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.

查看更多
登录 后发表回答