Jak najlepiej używać konstruktora w większym projekcie i czy upychać w nim logikę?

Odpowiedz Nowy wątek
2019-01-31 20:06
0

Joł,
Od pewnego czasu zacząłem robić ciut większe projekty, aby poćwiczyć oop. Przy najbliższej okazji zacząłem się zastanawiać jak wygląda realne użycie konstruktorów klas - czy tylko są używane do inicjalizowania składowych czy też mają inną rolę. Przykład:

Class::Class() {
(...) //inicjalizacja składowych na paręset linijek z listą czy bez
}
Class::Class() {
(...) //wywoływanie metod inicjalizujących składowe
}

Czy właśnie tak powinno wyglądać wywoływanie konstruktorów czy jest to niewydajne/brzydkie/bzdurne użycie? Może w ogóle lepiej jest nie męczyć aż tak konstruktora i zostawić sprawę metodom, które zrobią za niego robotę? Jak Wy używacie konstruktorów? Czy jest odgórnie ustalone ile powinien mniej więcej mieć loc taki konstruktor czy to bez znaczenia dopóki wszystko jest czytelne?
Pozdro


𝓜𝓮𝓷𝓽𝓪𝓵
𝓐𝓫𝓾𝓼𝓮
𝓣𝓸
𝓗𝓾𝓶𝓪𝓷𝓼

Pozostało 580 znaków

2019-01-31 20:21
1

Konstruktory wykorzystuje się nie tylko do inicjalizowania składowych ale też do pozyskiwania zasobów, poczytaj o idiomie RAII (https://en.cppreference.com/w/cpp/language/raii). Co do wywoływania metod w konstruktorach to często taki wzorzec widuję, jednak nie jestem fanem tego podejścia. Z jednej strony kod staje się nieco bardziej czytelny ale to też może oznaczać, że klasa jest zbyt duża i powinna być przemyślana jeszcze raz (np. rozdzielona). Dodatkowo C++ 11 wprowadził coś takiego jak delegating constructors, czyli w skrócie jeden konstruktor może wywoływać inny z tej samej klasy - nie trzeba klepać niemal tego samego kodu w konstruktorach (właśnie wtedy osobne metody miały sens). Należy również pamiętać, że mechanizm wywołań wirtualnych nie działa w konstruktorach i destruktorach.
Nie zapominaj też o liście inicjalizacyjnej.
I odpowiadając na Twoje ostatnie pytanie - przejrzystość kodu to podstawowa zasada.


Pozostało 580 znaków

2019-01-31 21:11
1

Nie ma odgórnych ustaleń, poza dedykowaną rolą - konstruktor powinien niejako przygotować obiekt do użytku, tak aby zaraz po jego utworzeniu dało się z niego korzystać. Raczej należy unikać sytuacji, jeśli tylko oczywiście możemy, że najpierw obiekt trzeba utworzyć, potem musieć robić na nim jakieś obiekt.init1(), obiekt.init2() (a już Swarogu broń w określonej kolejności) bo inaczej nie będzie zachowywał się poprawnie.
Czy wykorzystywać wewnątrz niego jakieś metody, też nie ma na to reguły, zależy od kontekstu. Ot przykład konstruktora z jednej z moich klas:
(@Marooned @Adam Boduch - Panowie, a gdzie znacznik spoiler/spoiler/?)

WeaponsDatabase& WeaponsDatabase::getInstance()
{
    static WeaponsDatabase instance;
    return instance;
}

WeaponsDatabase::WeaponsDatabase()
{
    QVector<QString> weaponFilesToExclude = DatabasesManager::getInstance().getFilesToExclude("WeaponsDatabase");

    QDir weaponsDir(DatabasesManager::getInstance().getJsonDataPath() + "/weapon", "*.json", QDir::Name|QDir::IgnoreCase, QDir::Files);

    QStringList weaponsFiles = weaponsDir.entryList();
    foreach (const QString &weaponFile, weaponsFiles)
    {
        if (weaponFilesToExclude.contains(weaponFile))
        {
            continue;
        }

        QFile file(weaponsDir.absolutePath() + '/' + weaponFile);
        if (file.open(QFile::ReadOnly))
        {
            //get json object for a weapon
            QJsonObject json = QJsonDocument::fromJson(file.readAll()).object();
            file.close();

            //the weapon does not exist yet, so it must be built and added to map of WeaponData
            WeaponData *weapon = new WeaponData;
            weapon->slotSize = json.value("InventorySize").toInt();
            weapon->tonnage = json.value("Tonnage").toInt();
            weapon->heat = json.value("HeatGenerated").toInt();
            weapon->damage = json.value("Damage").toInt();
            weapon->stabilityDamage = json.value("Instability").toInt();
            weapon->heatDamage = json.value("HeatDamage").toInt();
            weapon->startingAmmoCapacity = json.value("StartingAmmoCapacity").toInt();
            weapon->ammoCategory = json.value("AmmoCategory").toString();
            weapon->type = json.value("ComponentType").toString();
            weapon->subType = json.value("WeaponSubType").toString();
            weapon->bonusValueA = json.value("BonusValueA").toString();
            weapon->bonusValueB = json.value("BonusValueB").toString();
            weapon->name = json.value("Description").toObject().value("UIName").toString();

            QString hardpoint = json.value("Category").toString();
            SettingsContainer &settings = SettingsContainer::getInstance();
            if (hardpoint == "Ballistic")
            {
                weapon->hardpoint = HardpointTypes::Ballistic;                
                weapon->color = settings.getColorForEquipment(HardpointTypes::Ballistic);
            }
            else if (hardpoint == "Energy")
            {
                weapon->hardpoint = HardpointTypes::Energy;
                weapon->color = settings.getColorForEquipment(HardpointTypes::Energy);
            }
            else if (hardpoint == "Missile")
            {
                weapon->hardpoint = HardpointTypes::Missile;
                weapon->color = settings.getColorForEquipment(HardpointTypes::Missile);
            }
            else//the only possible hardpoint type left is support/antipersonnel
            {
                weapon->hardpoint = HardpointTypes::Antipersonnel;
                weapon->color = settings.getColorForEquipment(HardpointTypes::Antipersonnel);
            }

            //for now all weapons can be put anywhere where they fit.In the future however there should be added more conditions after the "All" check
            QString allowedLocations = json.value("AllowedLocations").toString();
            if (allowedLocations == "All")
            {
                weapon->allowedLocations = static_cast<MechLocations::Location>(weapon->allowedLocations | MechLocations::All);
            }

            //store the weapon in a map with same QString ID as it is in .JSON
            QString componentDefID(weaponFile);
            componentDefID.chop(5);//get rid of .json from the end

            weapons.insert(componentDefID, weapon);
        }
    }
}

Z kolei konstruktor bardzo podobnej klasy wygląda tak:

EquipmentDatabase &EquipmentDatabase::getInstance()
{
    static EquipmentDatabase instance;
    return  instance;
}

EquipmentDatabase::EquipmentDatabase()
{
    createEquipmentMap("AmmunitionBoxDatabase", "/ammunitionBox", Categories::ammo, true);
    createEquipmentMap("JumpJetsDatabase", "/jumpjets", Categories::jumpJets);
    createEquipmentMap("HeatsinksDatabase", "/heatsinks", Categories::heatsinks);
    createEquipmentMap("ActuatorsDatabase", "/upgrades/actuators", Categories::actuators);
    createEquipmentMap("CockpitModsDatabase", "/upgrades/cockpitMods", Categories::cockpitMods);
    createEquipmentMap("GyrosDatabase", "/upgrades/gyros", Categories::gyros);
    createEquipmentMap("TargetingTrackingDatabase", "/upgrades/targetTrackingSystem", Categories::computers);
}

void EquipmentDatabase::createEquipmentMap(const QString &exclusionsDatabaseName, const QString &equipmentDirectoryName, const QString &categoryName, bool isAmmo)
{
//robi w sumie to samo co klasa wyżej - wczytywanie danych z .json-ów i tworzenie na ich podstawie mapy
}

w tym createEquipmentMap z grubsza jest to samo, co w konstruktorze WeaponsDatabase, ale ponieważ każda z kategorii różniła się tylko niuansami takimi jak położenie folderu zawierającego .json-y z danymi to zamiast 7x powtarzać niemal identyczny kod wydzieliłem to do osobnej funkcji. Problem był z amunicją, bo ona ma inny schemat kolorystyczny - z tym poradziłem sobie wprowadzając dodatkowy parametr, domyślnie false.


"Sugeruję wyobrazić sobie Słońce widziane z orbity Merkurego, a następnie dupę tej wielkości. W takiej właśnie dupie specjalista ma teksty o wspaniałej atmosferze, pracy pełnej wyzwań i tworzeniu innowacyjnych rozwiązań. Pracuje się po to, żeby zarabiać, a z resztą specjalista sobie poradzi we własnym zakresie, nawet jeśli firma mieści się w okopie na granicy obu Korei."
-somekind,
konkretny człowiek-konkretny przekaz :]
edytowany 1x, ostatnio: MasterBLB, 2019-01-31 21:14
Pokaż pozostałe 2 komentarze
Ja bym próbowałbym przerobić to tak, by kod korzystał z meta danych (w końcu to jest Qt). Co zupełnie uprościło by przetwarzanie JSon-ów. Wtedy wystarczy opisać klasę reprezentującą obiekt JSona i tyle, całą magię zrobi się raz za pomocą meta danych (pewnie znalazł by się gotowiec). - MarekR22 2019-02-01 11:44
Przybliż proszę co masz na myśli Bracie @MarekR22. Ewentualnie jak zagadnienie jest obszerniejsze to na PW. - MasterBLB 2019-02-01 14:00
iterujesz po JSon-ie bierzesz nazwę pola, sprawdzasz czy jest takie property o tej nazwie w obiekcie docelowym, jeśli jest sprawdzasz jego typ. Na podstawie tego typu wartość JSon-a, konwertuje się, a potem robi set na pasującym property. Funkcja działająca w ten sposób ustawi wszystkie wartość WeaponData (lub innego typu) bez pisania boiler plate code. Jak masz dużo obiektów reprezentujących JSon-a to zaoszczędza się dużo pisania i głupich błędów wynikających z literówek. - MarekR22 2019-02-01 14:42
Hmmm brzmi całkiem nieźle. Niestety, ten JSON na jakim operuję jest niepotrzebnie skomplikowany, w sensie zawiera sporo podobiektów, tablic itd. Zrobienie owej iteracji tak, żeby radziła sobie z taką budową JSONa, i jeszcze niektóre wartości traktowała w specjalny sposób to imo za dużo zachodu. - MasterBLB 2019-02-01 15:01
Nie jest to łatwe to zrobienia, ale zrobi się raz, a używa potem 1000 razy, więc sumarycznie warto. Zresztą pogooglam, może już ktoś to zrobił. - MarekR22 2019-02-01 15:04

Pozostało 580 znaków

2019-01-31 21:20
1
Sunnydev napisał(a):

(...) //inicjalizacja składowych na paręset linijek z listą czy bez

Raczej długość funkcji, metod, konstruktorów, destruktorów nie powinna przekraczać 10 linijek. To pozwala uniknąć błędów i poprawia czytelność.
O zasadach dot. konstruktorów możesz poczytać tutaj -> link

Pozostało 580 znaków

2019-02-01 11:35
0

Co twoje pytanie ma wspólnego z OOP? Bez polimorfizmu (statyczny lub dynamiczny) nie ma OOP, a twój problem nie ma nic wspólnego z polimorfizmem.

Zwykle to standardy kodowania danego projektu, określają preferowane rozwiązanie.

Jaka jest różnica dla kompilatora? W większości wypadków dosłownie żadna. W C++ obowiązuje zasada "AS IF", wiec sposób zapisu kodu ma drugorzędne znaczenie.
Ma być czytelne dla developerów.


Jeśli chcesz pomocy, NIE pisz na priva, ale zadaj dobre pytanie na forum.
edytowany 2x, ostatnio: MarekR22, 2019-02-01 11:48
Po prostu podczas ćwiczenia obiektówki zrodziła się myśl o tych konstruktorach i tyle. Z resztą dlaczego uważasz, że to nie ma nic wspólnego z oop? - Sunnydev 2019-02-01 11:48
patrz update. - MarekR22 2019-02-01 11:49
czyli jeśli stworzę klasę, konstruktory i wszystko inne co z nią związane poza polimorfizmem to nie będzie to oop? to czym w takim razie to będzie? - Sunnydev 2019-02-01 11:52

Pozostało 580 znaków

Odpowiedz
Liczba odpowiedzi na stronę

1 użytkowników online, w tym zalogowanych: 0, gości: 1, botów: 0