Я боролся с этой проблемой дизайна в течение некоторого времени. Я приложу все усилия, чтобы объяснить, что я пытаюсь сделать, и различные подходы, которые я видел, что я пытаюсь и почему.
Я работаю в научной вычислительной среде, где я неоднократно имею дело с одними и теми же объектами. Представьте себе галактику, которая содержит солнечные системы, каждая солнечная система содержит планетные системы, а каждая планетная система содержит спутники. С этой целью я рассматриваю ситуацию как ситуацию «имеет», и поэтому я использовал композицию, чтобы дать галактике доступ к ее солнечным системам, а каждой солнечной системе — к планетным системам, которые имеют доступ к своим лунам: каждой категории быть своим собственным классом.
Часто бывает, что различные проблемы, над которыми я работаю, содержат разные типы данных об этих объектах. И, когда становятся доступны разные типы данных, я могу делать определенные вещи с моими объектами. Итак, когда у меня есть доступный тип данных 1, я создаю следующие классы
class GalaxyOne { /* … */ };
class SolarSystemOne { /* … */ };
class PlanetOne{ /* … */ };
class MoonOne{ /* … */ };
И когда у меня есть доступный мне тип данных 2, я создаю
class GalaxyTwo { /* … */ };
class SolarSystemTwo { /* … */ };
class PlanetTwo{ /* … */ };
class MoonTwo{ /* … */ };
Сложные аспекты каждого класса рассматриваются с помощью контейнерных переменных, таких как векторы, которые содержат указатели на содержащиеся классы. Например, внутри каждого класса Galaxy можно найти.
class GalaxyTwo{ /* … */
protected:
std::vector<SolarSystemTwo*> solarSystems;
/* … */
};
Трудность возникает, когда я хочу объединить классы в классы более высокого порядка.
class GalaxyOneTwo: public GalaxyOne, public GalaxyTwo{
/* Additional methods */
};
Однако это создает проблему неоднозначности в векторе solarSystems, поскольку GalaxyOneTwo будет иметь его версию от GalaxyOne и GalaxyTwo. Кроме того, векторы, которые он наследует, содержат указатели на объекты, которые не относятся к типу SolarSystemOneTwo, что необходимо. Итак, я подумал, что смогу создать шаблонный класс, который наследует все мои объекты, откуда я помещаю все свои переменные контейнера.
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 */
};
Этот подход очень хорошо работает для тех основных типов Galaxy (GalaxyOne и GalaxyTwo). Однако всякий раз, когда я пытаюсь создать комбинированный тип галактики, я получаю все виды неоднозначности.
class GalaxyOneTwo: public GalaxyOne, public GalaxyTwo, public PrimitiveGalaxy<MoonOneTwo,PlanetOneTwo,SolarSystemOneTwo>{
/* … */
};
Я получаю неоднозначность, если использую solarSystems в любом методе, определенном в GalaxyOneTwo, потому что он определяется три раза, один раз через каждую унаследованную версию от GalaxyOne и GalaxyTwo и третий раз через GalaxyOneTwo.
Я могу избавиться от этой двусмысленности, будучи конкретным и используя
PrimitiveGalaxy :: SolarSystems
каждый раз ссылаться на правильный вектор, но это нежелательно, потому что требует МНОГО дополнительной типизации и синтаксиса.
Я рассмотрел использование дружбы между каждым типом галактики и примитивной галактикой, но это требует подобного уровня многословного письма.
У меня есть предположение, что пространства имен могут упростить мое написание кода, но я не уверен, как определить пространство имен так, чтобы в пределах пространства имен, определенного для GalaxyOneTwo, любая ссылка на solarSystems была ссылкой на
PrimitiveGalaxy :: SolarSystems
Редактировать:
Обратите внимание, что единственная разница между GalaxyOne и GalaxyTwo — это НЕ тип класса, содержащегося в solarSystems. Есть много различий, потому что каждый класс имеет дело с различными данными, относящимися к галактике. Таким образом, я создаю разные классы, которые будут иметь разные переменные состояния, методы получения и установки для этих переменных состояния и методы для вычисления и печати данных. SolarSystems — это пример функции, которая доставляет мне проблемы, поэтому я описал ее здесь. Когда GalaxyOneTwo создан, он будет использовать те же данные, которые используются для GalaxyOne и для GalaxyTwo, и поэтому я хочу наследовать все их переменные и методы. И поскольку данные могут комбинироваться различными способами, мне нужно создать новые методы для этого в GalaxyOneTwo. Вот некоторые из многих различий, которые побуждают меня использовать наследование. Тем не менее, именно контейнерные переменные, которые учитывают композицию, доставляют мне проблемы. Внутри каждого класса solarSystem будет один и тот же вектор, дающий им доступ к их планетам, и так далее, и тому подобное.
Редактировать:
Для вопроса, специально посвященного моей философии дизайна здесь в целом (в отличие от этих вопросов акцент на попытке решить мою текущую попытку проектирования), смотрите следующую ссылку:
Руководство по созданию дизайна для составных классов с множественным наследованием в C ++
Возможно, вы могли бы изменить модели данных, чтобы реализовать что-то на основе компонентов. Каждый компонент может содержать ту же информацию о состоянии, которую вы упомянули для разных типов. И вы могли бы наследовать виртуальные функции
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...
Затем вы можете наследовать различные типы солнечных систем или галактик, чтобы создавать особые случаи и заполнять вызовы виртуальных функций.
class CoolSolarSystem : public SolarSystem
{
// Special variables
// Fill in the virtual function
void sharedSolarSystemFunction();
};
Затем вы можете заполнить контейнеры внутри разных базовых типов указателями на ваши специальные типы. Вы сможете избежать возврата к специальному типу, если вызовы виртуальной функции обрабатывают достаточно этой информации.
Я думаю, что вы должны иметь один class Galaxy
, один class SolarSystem
и тд GalaxyOne
, GalaxyTwo
SolarSystemOne
, SolarSystemTwo
и т. д. — это только разные объекты, полученные из этих классов.
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;
Если у нас нет возможности использовать этот простой подход … Давайте посмотрим ваш:
class GalaxyOneTwo: public GalaxyOne,
public GalaxyTwo,
public PrimitiveGalaxy<MoonOneTwo,PlanetOneTwo,SolarSystemOneTwo>{
/* … */
using PrimitiveGalaxy<MoonOneTwo,PlanetOneTwo,SolarSystemOneTwo>::solarSystems;
GalaxyOneTwo(){solarSystems.reserve(10);}
};
Здесь у вас есть три частных вектора: (используя using
Вы делаете это прямым доступным)
std::vector<SolarSystemOne* > GalaxyOne::solarSystems;
std::vector<SolarSystemTwo* > GalaxyTwo::solarSystems;
std::vector<SolarSystemOneTwo*> solarSystems; //GalaxyOneTwo::solarSystems;
Это то, что тебе надо? Сделать это защищенным?
Вам необходимо абстрагировать общий материал между версиями «One» и «Two» в набор базовых классов «Common»:
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
};
Обратите внимание на использование virtual
— требуется, чтобы правильно работало множественное наследование.
Так что, если я правильно понял ваш вопрос:
Это звучит как случай для повышение :: любой
Итак, теперь ваша Галактика:
#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...
};
Конечно, это на самом деле ничего не делает, но позволяет вам сгруппировать их. Чтобы сделать что-нибудь полезное (вызвать метод), вам все равно придется определить тип, привести его к этому типу, а затем запустить необходимый код.
Тем не менее, это позволяет вам группировать объекты разных типов вместе, что, я думаю, то, что вам нужно с GalaxyOneTwo. Вот Дополнительная информация о том, как вы можете сделать это.