Моделирование планетарной системы

141
07 сентября 2019, 11:40

Задание заключается в том, чтобы на примере не менее 5 классов, которые содержат не менее 4 функций использовать концепции ООП: инкапсуляцию, наследование, полиморфизм, перегрузка.

На правильном ли я пути?

Я только учусь, поэтому немного не понимаю как можно использовать инкапсуляцию и перегрузку на примере планетарной системы? И как можно отсортировать размеры планет с привязкой к самим планетам (чтобы потом можно было вывести список на экран).

#include <iostream>
#include <string>
using namespace std;
class Planet {
    private:
        std::string NamePlanet;
        std::string *ArrPlanet;
        const int sizePlanet;
        int NumberPlanet;
    public:
        Planet();
        ~Planet();
        void addPlanet();
        show();
};
class SpaceObject {
    private:
        std::string NameSpaceObj;
        std::string *ArrSpaceObject;
        const int sizeSpaceObj;
        int NumberSpaceObject;
    public:
        SpaceObject();
        ~SpaceObject();
        void addSpaceObj(); 
        show();         
};
class TypePlanet : public Planet {
    private:
        std::string NameTypePl;
    public:
        TypePlanet();
        ~TypePlanet();
        void addTypePlanet();
        show();
};
class SizePlanet : public Planet {
    private:
        int SizePl;
        int sizeSizePl;
        int *ArrSizePlanet;
        int NumberSizePl;
    public:
        SizePlanet();
        ~SizePlanet();
        void addSizePlanet();
        void SortSizePlanet();
        show();
};
class TypeSpObject : public SpaceObject {
    private:
        std::string NameTypeSpObj;
    public:
        TypeSpObject();
        ~TypeSpObject();
        void addTypeSpObj();
        show();
};
class SizeSpaceObject : public SpaceObject {
    private:
        int SizeSpObj;
    public:
        SizeSpaceObject();
        ~SizeSpaceObject();
        void addSizeSpaceObj();
        void SortSizeSpaceObj();
        show();
};
//--------------------------------------------------------------------------------------------------------
Planet::Planet(int maxSize) {
    sizePlanet(maxSize) {
        ArrPlanet = new Planet [sizePlanet];
        NumberPlanet = 0;
    }
}
Planet::~Planet() {
    delete [] ArrPlanet;
}
void Planet::add (Planet std::string NamePlanet){
    this->NamePlanet=NamePlanet;
    if (NumberPlanet < sizePlanet)
    ArrPlanet[NumberPlanet++] = NamePlanet;
}
SpaceObject::SpaceObject(int maxSize) {
    sizeSpaceObject(maxSize) {
        ArrSpaceObject = new SpaceObject [sizeSpaceObject]
        NumberSpaceObject = 0;
    }
}
SpaceObject::~SpaceObject() {
    delete [] ArrSpaceObject;
}
void SpaceObject::addSpaceObj(){
    if (NumberSpaceObject < sizeSpaceObject)
    ArrSpaceObject[NumberSpaceObject++] = value;
}
TypePlanet::TypePlanet() : Planet();
TypePlanet::~TypePlanet() : ~Planet();
void TypePlanet::addTypePlanet(TypePlanet std::string NameTypePl) : add (TypePlanet std::string NameTypePl)
SizePlanet::SizePlanet(int maxSize) {
    sizeSizePl(maxSize) {
        ArrSizePlanet = new SizePlanet [sizeSizePl];
        NumberSizePl = 0;
}
SizePlanet::~SizePlanet() {
    delete [] ArrSizePlanet;
}
void SizePlanet::addSizePlanet() {
}
void SizePlanet::SortSizePlanet() {
}
Answer 1

Поддерживаю AR Hovsepyan в том, что:

код нужно написать после того, как вы начнете правильно представлять что хотите получать на основе логических действий

Тем не менее мне стало интересно, и я решил набросать небольшой примерчик (с использованием с++11), чтобы наглядно продемонстрировать то, что вызвало у автора трудности.

Суть предметной области, которая пришла мне на ум, это графика (игра, визуализация чего-то).

Итак, у нас есть планета. Чем же ее выделить? Ну, например, каждая планета имеет имя, радиус, массу. Кроме этого (не вдаваясь в законы гравитации) радиус и масса будут влиять на ускорение свободного падения на этой планете. Кроме этого представим, что в нашем графическом приложении планета может взрываться, но взрываться по разному (об этом чуть ниже). В итоге, получаем класс Planet, который знает свои свойства, но не знает, как именно он будет взрываться.

Теперь выделим типы планет или лучше вежливо подсмотрим в Википедии и возьмем для примера земляного типа, газовых и ледяных гигантов. Они по разному реализуют функцию взрыва, в теории подставляя разную анимацию этого взрыва, разные изображения, плюс ледяные гиганты могут взрываться с разлетом глыб льда. На практике получаем три дочерних класса, на вид примерно одинаковых, но различающимися 1-2 словами в начале вывода cout. В итоге получаем классы TerrestrialPlanet, GasGiantPlanet, IceGiantPlanet.

Далее, представим, что информация о планетах, которая попадает в конструктор Planet, находится извне (БД, файл, т.п.) и что она уже попала в программу, правильно распарсилась и записалась в соответствующие переменные. Учитывая это, мы можем написать функцию-заглушку, которая будет правильно создавать планету, например, по ее имени и возвращать указатель на объект в куче. В итоге получаем функцию Planet* getConcretePlanetByName(const string &);.

В итоге в функции main получаем возможность работать с объектами планет, не заботясь о их типе и правильной инициализации (спасибо полиморфизму и наследованию) и создавать коллекции, не боясь случайно повредить внутреннее состояние объектов (спасибо инкапсуляции).

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
/*
 * Базовый класс планеты.
 */
class Planet
{
public:
    Planet(const string &name, double radius, double weight) : 
        name(name), 
        radius(radius),
        weight(weight)
    {
    }
    // виртуальный деструктор
    // если возникает вопрос "почему",то см. ссылку ниже
    virtual ~Planet() {}  
    // виртуальная функция взрыва, в которой и будет весь смак полиморфизма
    virtual void explode() = 0;
    // методы доступа 
    string getName() const { return name; }
    double getRadius() const { return radius; }
    double getWeight() const { return weight; }
    // если не ошибаюсь, это называется вычисляемым свойством
    double getAcceleration() const
    {
        const double G = 6.67e-11; // гравитационная постоянная
        return G * weight / (radius * radius);
    }
    // если я правильно понял автора на счет перегрузки
    // положим, что планета будет больше другой планеты, если больше ее радиус
    const bool operator > (const Planet &right)
    {
        return radius > right.radius;
    }
// ну и соответственно поля приватные для обеспечения инкапсуляции
private:
    string name;
    double radius;
    double weight;
};

/* 
 * Классы, реализующие разные типы планет.
 */
class TerrestrialPlanet : public Planet
{
public:
    using Planet::Planet;
    void explode() override
    {
        cout << "Terrestrial planet " << getName() << " was exploded with acceleration = " << getAcceleration() << endl;
    }
};
class GasGiantPlanet : public Planet
{
public:
    using Planet::Planet;
    void explode() override
    {
        cout << "Gas giant planet " << getName() << " was exploded with acceleration = " << getAcceleration() << endl;
    }
};
class IceGiantPlanet : public Planet
{
public:
    using Planet::Planet;
    void explode() override
    {
        cout << "Ice giant planet " << getName() << " was exploded with acceleration = " << getAcceleration() << endl;
    }
};

/*
 * Функция заглушка, в которой уже есть все готовые 
 * данные для инициализации объектов 
 */
Planet* getConcretePlanetByName(const string &name)
{
    // все числа взяты из гугла, радиус в метрах, масса в килограммах
    double earthRadius = 6371000;
    double earthWeight = 5.97e24;
    double jupiterRadius = 69911000;
    double jupiterWeight = 1.89e27;
    double neptuneRadius = 24622000;
    double neptuneWeight = 1.02e26;
    if (name == "Earth")
        return new TerrestrialPlanet("Earth", earthRadius, earthWeight);
    if (name == "Jupiter")
        return new GasGiantPlanet("Jupiter", jupiterRadius, jupiterWeight);
    if (name == "Neptune")
        return new IceGiantPlanet("Neptune", neptuneRadius, neptuneWeight);
    return nullptr;
}
/*
 * И самое вкусное - функция main:
 */
int main()
{
    vector<Planet*> planets {                // какой-то набор планет
        getConcretePlanetByName("Earth"),
        getConcretePlanetByName("Jupiter"),
        getConcretePlanetByName("Neptune")
    };
    sort(planets.begin(), planets.end(),     // сортировка
        [](Planet* a, Planet* b) -> bool
    { 
        return *a > *b;
    });
    for (auto &planet : planets) {            // полиморфизм
        planet->explode();
        delete planet;
        planet = nullptr;
    }
    return 0;
}

В результате получаем такой вывод:

Gas giant planet Jupiter was exploded with acceleration = 25.7927                                                                                                                  
Ice giant planet Neptune was exploded with acceleration = 11.2222                                                                                                                  
Terrestrial planet Earth was exploded with acceleration = 9.81036

Что мы имеем в результате:

  1. Инкапсуляция. У пользователей класса нет возможности менять значения полей name, radius, weight. Будь у них эта возможность, это рано или поздно могло бы привести к изменению этих полей и, как следствие, неправильной работе функции getAcceleration() (кто-то случайно, например в конструкторе дочернего класса, чисто из благих соображений "инициализируй переменную перед использованием" сделал бы radius = 0.. Как бы тогда отработала функция?).
  2. Наследование. Классическая реализация отношения IS-A от абстрактного к более частному (Terrestrial planet is a planet). Объединяет в себе смысловое и механическое наследование.
  3. Полиморфизм. Возможность вызывать интерфейсную функцию explode для каждого конкретного типа, не заморачиваясь о том, какой именно тип используется.
  4. Перегрузка. Используется перегрузка оператора для упрощения синтаксиса сравнения объектов.

По хорошему не мешало бы поиграться с точностью вычислений. Раз взялся за перегрузку операторов сравнения, то тоже не мешало бы довести ее до ума. Проверка на nullptr тоже не помешала бы при боевом крещении. Но, думаю, для демонстрационного примера вполне подходит.

P.S. обещанная ссылочка на информацию про виртуальный деструктор: Майерс Скотт - Эффективное использование C++

Answer 2

Дело в том, что код нужно написать после того, как вы начнете правильно представлять что хотите получать на основе логических действий.

Если планета имеет имя, то она должна быть классом одиночкой (singleton\, поскольку планета с конкретным именем всего одна. При этом вы должны определять структуру (класс) для свойств планет, и каждая планета должна иметь конкретный объект Planet_traits (вы должны написать этот класс) и другие данные (например статический объект_звезда, вокруг которой она крутится...

Если же вы решили прибегнуть к наследованию, то планета должна отражать общее понятие, а каждый производный класс должен отражать планеты с конкретным свойством. Например: Газовые_гиганты, карликовые_планеты и т.д. При этом, абстракция полностью зависит от того, с точки зрения кого вы хотите смотреть на них. Для астрономов одна абстракция, для обычного человека другая (ему может интересовать только расстояние от земли, есть ли жизнь или нет, и доступна ли она нашему взору...

Все это называется архитектурой, и вы выбрали немного сложную тему для новичка (или для программиста_одиночки).

Инкапсулировать нужно те данные, которые не нужны клиенту. Те функции, которые для каждого типа планет выполняется по разному нужно сделать виртуальными, и тогда у вас обеспечится полиморфизм.

READ ALSO
Неправильно работает функция rand() в c++ [закрыт]

Неправильно работает функция rand() в c++ [закрыт]

Хочу получить рандомные числа от 0 до 5Этот код возвращает: 11 23 35 41 55 65 74 Почему?

138
Прочитать структуры из вектора

Прочитать структуры из вектора

Есть вектор структур:

128
Найти максимальный элемент массива [закрыт]

Найти максимальный элемент массива [закрыт]

Найти максимальный элемент в массиве ai (i=1, , n), используя очевидное соотношение max(a1, , an) = max[max(a1, , an–1), an]Использовать рекурсивную и нерекурсивную...

135
Как перенаправить ввод-вывод через pipe?

Как перенаправить ввод-вывод через pipe?

Создаю pipe, запускаю программу - итог висякКак проверить что pipe не нужно читать - виснет на чтение pipe

106