OOP-Style: Tasks verarbeiten



  • Hallo. Folgende Situation: Ich habe verschiedene Klassen, die klassenspezifische Tasks nacheinander verarbeiten sollen.

    • Jeder Task benötigt eine gewisse Zeitdauer und hat weitere Kosten (int-Werte)
    • Vor dem Hinzufügen der Tasks würde ich gerne prüfen, ob der Nutzer sich den Task leisten kann

    Mein derzeitiger Ansatz wäre der Folgende (ungetestet aus dem Kopf getippt):

    class Task {
    virtual int duration_ms() = 0;
    virtual int costs() = 0;
    };
    
    class ObjectA {
    public:
    // Klassenspezifische Tasks
    class TaskUpgrade1 : public Task {
        // implementierung der virtual Funktionen
    };
    
    class TaskUpgrade2 : public Task {
        // implementierung der virtual Funktionen
    };
    
    // Könnte man auch noch verallgemeinern
    void addTask(Task* pTask) {
        if(typeid(*pTask) == typeid(TaskUpgrade1) || typeid(*pTask) == typeid(TaskUpgrade2)) {
            m_vecTasks.push_back(pTask);
        }
    
    private:
        std::vector<Task*> m_vecTasks; // Könnte man auch noch verallgemeinern
    };
    
    class ObjectB {
    public:
    // Klassenspezifische Tasks
    class TaskProduceThis: public Task {
        // implementierung der virtual Funktionen
    };
    
    class TaskProduceThat: public Task {
        // implementierung der virtual Funktionen
    };
    
    class TaskUpgrade: public Task {
        // implementierung der virtual Funktionen
    };
    
    // Könnte man auch noch verallgemeinern
    void addTask(Task* pTask) {
        if(typeid(*pTask) == typeid(TaskProduceThis) || typeid(*pTask) == typeid(TaskProduceThat) || typeid(*pTask) == typeid(TaskUpgrade)) {
            m_vecTasks.push_back(pTask);
        }
    
    private:
        std::vector<Task*> m_vecTasks; // Könnte man auch noch verallgemeinern
    };
    

    In der Verarbeitungsmethode der Klasse(n) würde dann wieder geprüft werden, was aktuell für ein Task vorliegt und dann anhand der Zeit&Kosten verarbeitet werden. Alternativ könnte man die Prüfung beim hinzufügen weglassen, da die Klasse später eh nochmal prüft, was für ein Task aktuell anliegt. Unbekannte könnten dann ignoriert werden (default: break).

    Ich bin aber noch nicht ganz glücklich damit. Wie würdet ihr das lösen?



  • Nachtrag:

    Ein Nachteil wäre irgendwie die Benutzung. Angenommen der Benutzer klickt einen Button für TaskProduceThat:

    onButton_TaskProduceThat:
    
    ObjectB::TaskProduceThat task;
    
    if(task.costs() < availableResources()) {
    availableResources -= task.costs();
    pObjectB->addTask(new 
    ObjectB::TaskProduceThat());
    }
    

    Man sieht, die Benutzung ist recht holprig. Wenn der Benutzer z.B. den Task abbrechen will, soll er auch die Kosten wiederbekommen 🙄



  • Mich stören vor allem zwei Dinge:

    1. Benutzung von rohen, besitzenden Zeigern. std::unique_ptr ist dein Freund.
    2. die typeid Überprüfungen. Müssen Task heterogen verwaltet werden (leben Tasks von ObjectA und ObjectB in einem Container)? Wenn ja würde ich eine Hierachiestufe zwischen Task und den konkreten Tasks einfügen:
    Task
      +--------+--------+
    TaskA             TaskB
                 +------+------+
             TaskBThis      TaskBThat
    

    Damit kann die Signatur auf z.B. ObjectB::addTask( TaskB* Task ) geändert werden.

    Ansonsten würde ich auf Task verzichten und nur TaskA und TaskB benutzen. Hängt jedoch von der Zahl der Basisklassen ab. Wieviele A, B, ... gibt es denn?



  • Es gibt so ca. 14 unterschiedliche Objekte, wobei einige kaum Tasks verarbeiten, anderen wiederum recht viele.

    Die Idee mit der Hierarchiestufe ist schon einmal sehr gut. Was mich interessieren würde: Würdest du das gänzlich anders lösen? Ich bin da echt offen für Ansätze 🙂


Anmelden zum Antworten