If you cannot understand the name of the question from the very beginning, it is not your fault - I could not come up with a better description. Here is an explanation of the problem, which may be a little long, so we apologize in advance.
In the original 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 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 much more in the future.
Now I decided to divide the Individual class into a base class and two derived classes, for example TypeAIndividual and TypeBIndividual, because each of them has members and attributes that the other does not need (they also have several members and attributes through the base class). Therefore, I have a 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 also needs to be divided 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 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 use void func(int _individual_id) , which need access to individuals of the appropriate type. But the base class Ecosystem no longer contains pointers to people, since std::map are in every derived class, and not in the base class.
My question is: how can I access the appropriate personality type ( TypeAIndividual or TypeBIndividual ), while avoiding the implementation of a separate void func(int _individual_id) in each class derived from Ecosystem? In other words, is there a way to save func() in the base class so that when I change it, I donβt need to make changes to the derived classes? In a real program, there are dozens of functions, such as func() , that take only int as a parameter. In addition, some of these functions accept individual identifiers from other structures of the Ecosystem class, so I cannot just pass a pointer to TypeAIndividual or TypeBIndividual .
Things I Considered
Combining TypeAIndividual and TypeBIndividual back into the generic Individual class with all the data structures needed for both derived classes. It strikes me as a particularly clumsy way to do something, but at least it will work.
Creating virtual objects func() and Co. and their implementation in TypeAEcosystem and TypeBEcosystem . This means that if I want to make changes to any of the functions, I must change both implementations (= service nightmare).
Having only one Ecosystem class that contains std::map two types of individuals, for example:
// 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 age: " << type_a_individuals[_individual_id]->get_age() << std::endl; } else { std::cout << "Individual age: " << type_b_individuals[_individual_id]->get_age() << std::endl; } } };
This will require inserting a check into each function, which is almost as bad in terms of maintainability as the presence of functions in individual classes.
Note Although I would very much like to avoid the omissions of pointers, I would consider raising and / or lowering the rating (in the worst case ...) if it solves the problem.
Any suggestions are welcome!
Change 1Thanks everyone for the fantastic answers! As amit and Chris suggested, both looked at my Ecosystem class and, of course, it was too cumbersome. I have moved member functions to other classes, and now I have up to four or five core functions of the Ecosystem class. The Ecosystem class is located in the library and provides an interface for experimenting with individuals, but I do not want users to be able to directly manipulate Individual and other classes, so I cannot completely abandon it.
I liked all the suggestions, there are several ingenious solutions. However, the one Chris suggested immediately drew my attention to the fact that he was very neat and allowed me to have one Ecosystem class, and not three separate classes (base and two derivatives). The type of individual can be specified in the configuration file, and I can spawn several ecosystems from different configuration files within the same experiment. This is an accepted answer.
Thanks again for the constructive contribution!