If you can't understand the question title from the onset, it's not your fault - I couldn't think of a better description. Here is the explanation of the problem, which might be a bit lengthy, so apologies in advance.
In the initial version of my program, I had an Ecosystem class and an Individual class:
// Very simplified, for illustration purposes
class Ecosystem
{
protected:
// The int is just the ID of the individual.
std::map<int, std::shared_ptr<Individual> > individuals;
public:
Ecosystem();
void func(int _individual_id)
{
std::cout << "Individual's age: "
<< individuals[_individual_id]->get_age()
<< std::endl;
}
void routine(int _individual_id)
{
// Another function working via
// the pointers in individuals.
}
// More such functions...
};
class Individual
{
protected:
int age;
public:
Individual();
inline int get_age() const
{
return age;
}
};
The Ecosystem class contains dozens of functions, and I will add a lot more in the future.
I have now decided to split the Individual class into a base class and two derived classes, say TypeAIndividual and TypeBIndividual, because they each have members and attributes that the other one does not need (they also share a few members and attributes via the base class). So I have the base Individual class and two derived classes:
class TypeAIndividual : public Individual
{
protected:
// Data structures specific to individuals of type A
public:
TypeAIndividual();
};
class TypeBIndividual : public Individual
{
protected:
// Data structures specific to individuals of type B
public:
TypeBIndividual();
};
The problem is that the ecosystem now also needs to be split into TypeAEcosystem and TypeBEcosystem:
class Ecosystem
{
protected:
// Holding pointers to the base Individual class is pointless (pun not intended)
// std::map<int, std::shared_ptr<Individual> > individuals;
public:
Ecosystem();
// I want to keep func() in the base class
// because it only accesses attributes and
// members common to both classes derived
// from Individual.
void func(int _individual_id)
{
// Hmmmm...
// The pointers don't live in the Ecosystem class any more!
std::cout << "Individual's age: "
<< individuals[_individual_id]->get_age()
<< std::endl;
}
// OK to implement in each class
// derived from Ecosystem.
virtual void routine(int _individual_id) = 0;
};
class TypeAEcosystem : public Ecosystem
{
protected:
// Pointers to individuals
// of the corresponding type.
std::map<int, std::shared_ptr<TypeAIndividual> > individuals;
public:
TypeAEcosystem();
// Reimplementing routine() is OK
// because it does things specific to
// this individual type.
virtual void routine (int _individual_id)
{
// Operate on data structures particular
// to this type of individual.
}
};
class TypeBEcosystem : public Ecosystem
{
protected:
// Pointers to individuals
// of the corresponding type.
std::map<int, std::shared_ptr<TypeBIndividual> > individuals;
public:
TypeBEcosystem();
// Reimplementing routine() is OK
// because it does things specific to
// this individual type.
virtual void routine (int _individual_id)
{
// Operate on data structures particular
// to this type of individual.
}
};
TypeAEcosystem and TypeBEcosystem both use void func(int _individual_id), which needs to access individuals of the corresponding type. But the base class Ecosystem doesn't contain pointers to individuals any more because the std::maps are in each derived class and not in the base class.
My question is: how can I access the appropriate type of individual (TypeAIndividual or TypeBIndividual) while avoiding implementing separate void func(int _individual_id) in each class derived from Ecosystem? In other words, is there a way to keep func() in the base class so that when I change it, I don't have to make changes to the derived classes? In the actual program, there are dozens of functions like func() which take just an int as a parameter. Also, some of those functions take individual IDs from other structures in the Ecosystem class, so I can't simply pass a pointer to TypeAIndividual or TypeBIndividual.
Things I have considered
-
Merging
TypeAIndividualandTypeBIndividualback into a commonIndividualclass with all the data structures necessary for both derived classes. This strikes me as a particularly clumsy way of doing things, but at least it will work. -
Making
func()& Co. virtual and implementing them inTypeAEcosystemandTypeBEcosystem. This means that if I want to make a change in any of the functions, I have to change both implementations (= a maintenance nightmare). -
Having only one
Ecosystemclass which holdsstd::maps of the two types of individuals, like this:// Seems clunky... class Ecosystem { protected: // Note: The Ecosystem can contain // one OR the other, but not both! // One map will always be empty. std::map<int, std::shared_ptr<TypeAIndividual> > type_a_individuals; std::map<int, std::shared_ptr<TypeBIndividual> > type_b_individuals; public: Ecosystem(); void func(int _individual_id) { // Check what type of individuals we // are working with and operate on the // appropriate container. if (type_a_individuals.size() > 0) { std::cout << "Individual's age: " << type_a_individuals[_individual_id]->get_age() << std::endl; } else { std::cout << "Individual's age: " << type_b_individuals[_individual_id]->get_age() << std::endl; } } };
This would require inserting a check in every function, which is almost as bad in terms of maintainability as having the functions in separate classes.
Note: Although I would very much like to avoid passing pointers around, I would consider upcasting and/or downcasting as appropriate (as a last resort...) if it solves the problem.
Any suggestions are welcome!
Edit 1
Thank you all for the fantastic responses! As suggested by both amit and Chris, and looked at my Ecosystem class and sure enough, it was too bulky. I moved member functions around into other classes and now I'm down to four or five essential functions in the Ecosystem class. The Ecosystem class resides in a library and provides an interface for conducting experiments with individuals, but I don't want users to be able to manipulate Individuals and other classes directly, so I can't do away with it completely.
I liked all suggestions, there are some ingenious solutions. That being said, the one proposed by Chris grabbed my attention immediately for being very neat and allowing me to have a single Ecosystem class rather than three separate classes (base and two derived). The type of individual can be specified in a config file, and I can spawn multiple ecosystems from different config files within the same experiment. This is the accepted answer.
Thank you again everyone for the constructive input!
via Chebli Mohamed
Aucun commentaire:
Enregistrer un commentaire