Design/Konzept-Frage: Observe-Pattern + Alternative zu RTTI
-
Hallo zusammen!

Ich knobel an folgendem Problem und weiß nicht so recht wie ich es lösen soll:
// "Job.h" enum JobStatus{ Waiting, InProgress, Done, Failed }; // Basisklasse aller Jobs class JobBase{ public: Job() : m_status(Waiting){}; virtual ~Job(){}; public: virtual void DoJob() = 0; JobStatus GetStatus() const { return m_status; } private: JobStatus m_status; }; // Klasse Jobverwalter: // // Nimmt Aufträge entgegen und arbeitet diese zeitversetzt ab. // Sobald Der Job bearbeitet werden soll wird die jeweilige // DoJob()-Methode aufgerufen. class JobVerwalter{ public: // Konstruktor, etc... public: AddJob(JobBase& job); private: std::map<int, JobBase> m_jobs; };Es gibt Jobs und einen JobVerwalter, der die Jobs verwaltet und die Durchfühung veranlasst (ruft
DoJob()auf). Nun gibt es Jobs die Daten erzeugen, die der Aufrufer dann auch irgendwie abholen möchte. Es gibt aber auch Jobs die für Ihre Tätigkeit zwar Daten benötigen, aber keine Daten zurück liefern. Alle Jobs sollen ein Status liefern können, damit man den Erfolg bzw. Misserfolg überwachen kann.// "VerarbeiteDatenJob.h" class VerarbeiteDatenJob : public Job{ public: VerarbeiteDatenJob(const DatenPtr& daten) : m_daten(daten){} public: void DoJob(){ //macht etwas mit m_daten } private: SmartDatenPtr m_daten; };// "ErzeugeDatenJob.h" class ErzeugeDatenJob : public Job{ public: ErzeugeDatenJob() : m_daten(){} public: void DoJob(){ /* befüllt m_daten */ } SmartDatenPtr GetData() const { return m_data; } private: SmartDatenPtr m_daten; };Nun habe ich an das Observer-Pattern gedacht, um den Aufrufer (als Observer) darüber zu informieren wann ein Job fertig oder fehlgeschlagen ist. (Nun ist die Frage, ob ich den Job beobachten lasse oder den JobVerwalter, der dann den Aufrufer darüber infomiert, dass für einen Job ein neuer JobStatus vorliegt.)
Ich würde dann bei der Benachtigung des Observers ein Zeiger auf den Job mitliefern. Nur ist dann die Frage, wie identifiziere ich den Job. Zuerst habe ich an RTTI gedacht, doch nach etwas Suche im Forum denke ich, dass das nicht sinnvoll ist (-->RTTI vermeiden).
Doch wie komme ich von der Job-Basisklasse nun zu dem eigentlichen Job, um z.B. die Daten abzurufen oder um festzustellen, welcher Job fehlgeschlagen ist?

Für Eure Hilfe wäre ich sehr dankbar!
Gruß Roger Wilco
-
Bald ist Weihnachten. Ich darf doch annehmen, daß Du Dir Mehr effektiv C++ programmieren | ISBN: 3827312752 gewünscht hast.
-
Hallo volkard,
anscheined stelle ich mich zeimlich dämlich an, wenn Du mir das Buch empfiehlst. Bis Weihnachten ist es noch lange hin, also an welchen Stellen hapert es bei mir? Ein paar Hinweise wären super...
P.S.: Ich habe die 3.Auflage von "Effektiv C++ 55 Möglichkeiten,..."
-
Roger Wilco schrieb:
anscheined stelle ich mich zeimlich dämlich an, wenn Du mir das Buch empfiehlst.
Nein, absolut nicht. Jenes Buch reitet aber recht ausführlich auf dem Dispatchen rum und wie man virtuelle Funktionen und Überladung hübsch benutzen kann, um die Bösen Gedanken an rtti/switch endgültig zum Teufel zu schicken. Daher dachte ich, daß Du es jetzt brauchst. Ist viel zu viel, als daß ich es hier kurzfassen könnte oder wollte.
(Darüberhinaus tut das Buch aber nichts. Ein Fall für Fernleihe via Stadtbücherei, weil man es nicht zweimal lesen mag.)
-
Designtechnisch würde ich sagen, ein Job ist ein Job, dass du die Implementierung um das Interface erweiterst ist evtl nicht das Beste, evtl solltest du über ein Quelle und Ziel nachdenken.
-
Dann ist das Job + JobVerwater + Observer-Pattern Konzept grundsätzlich bruachbar, ja? Nur um eine RTTI-Alternative müsste ich mir besorgen und implementieren?
Oder würdest Du abgesehen von der RTTI-Sache einen anderen Ansatz für mein Problem der zeitversetzten Jobs wählen?
-
Ist es nicht Teil des Sortierjobs, bei Jobende dem Auftraggeber die sortierten Daten zurückzugeben?
Ich sehe gar keine Observer und RTTI, sorry.Nur ein Problemchen, was man machen will, wenn Auftraggeber sterben, die noch offene Jobs haben. Och, im Zweifelsfall die Jobs in einen doppelt verketteten itrusiven Ring an den Auftraggeber hängen.
-
Hmm, jetzt versteh ich Dich nicht ganz.
Vielleicht nochmal zu dem "was ich möchte":
Ich möchte verschiedene Jobs abarbeiten lassen, ohne dass ich weiß wie lange so ein Job dafür braucht, oder ob er Aussicht auf Erfolg hat.
Manche Jobs sollen dem Auftraggeber Daten liefern, andere sollen Daten verschicken. Bei jedem Job muss der Auftraggeber wissen, ob der Job erfolgreich ausgeführt wurde. Bei den Jobs, die Daten liefern sollen, benötigt der Auftraggeber natürlich die Daten wenn sie vorliegen.
Daher dachte ich an das Observer-Pattern, damit der Auftraggeber informiert wird, wann ein Job beendet ist. Ich könnte die Jobs auch stupide pollen, aber das finde ich nicht besonders elegant. Vielleicht denke ich auch zu komliziert.
RTTI kam ins Spiel, da ich ja verschiedene Jobs habe: Die einen brauchen Daten mit denen sie arbeiten, die anderen holen+liefern Daten. Dabei gibt es unterschiedliche Datentypen mit den die unterschiedlichen Jobs arbeiten.
Der Jobverwalter ist dazu da, die Jobs abzuarbeiten und die zentrale Datenverbindung, die die Jobs benötigen zu steuern.
-
Ich dachte an
class JobBase{ public: virtual ~Job(){}; virtual void DoJob() = 0; }; class Sortierjob:public JobBase{ private: vector<int>& zuSortieren; Auftraggeber* auftraggeber; int vomAuftraggeberGewählteID; public: ... void DoJob(){ if(DoJobPrivate()) auftraggeber->binFertigMitSortieren(vomAuftraggeberGewählteID,zuSortieren); else auftraggeber->sortiernHatNichtGeklappt(vomAuftraggeberGewählteID); theOneAndOnlyJobmanager->removeAndDeleteJobTodayOrTomorrow(this); } }
-
Danke volkard für das Beispiel, jetzt habe ich Dich verstanden!

Das wäre in der Tat eine Möglichkeit. Hmm... muss mal drüber nachdenken... *grübel*
Der Auftraggeber müsste dann dafür sorgen, dass bei seiner Zerstörung die Jobs mit abgebrochen werden.
-
Roger Wilco schrieb:
Danke volkard für das Beispiel, jetzt habe ich Dich verstanden!

Das wäre in der Tat eine Möglichkeit. Hmm... muss mal drüber nachdenken... *grübel*
Der Auftraggeber müsste dann dafür sorgen, dass bei seiner Zerstörung die Jobs mit abgebrochen werden.Ok, dann hab ich Dich soweit und kann jetzt meine Rakete abschießen: Ich fürchte, was Du da anstellt, stellt sich in Wirklichkeit ganz anders dar. Du kennst die smalltalkige Sicht, daß Objekte sich Nachrichten senden. Eine Nachricht ist dabei ein Methodenaufruf. Du bastelst asynchrone Methodenaufrufe? Aha, daher auch das unwohle Gefühl bei dem vector&. Referenz auf fremde Daten? Ihgitt. Dem Job eine Kopie geben? Würg. Da stimmt was nicht.
Beschränken wir uns mal auf asynchrone Methodenaufrufe.class JobBase{ public: virtual ~Job(){}; virtual void DoJob() = 0; }; class Sortierjob:public JobBase{ private: vector<int>& zuSortieren; Auftraggeber* auftraggeber; int vomAuftraggeberGewählteID; public: ... void DoJob(){ auftraggeber->sortiereUndSagDirSelberBescheid(vomAuftraggeberGewählteID,zuSortieren); } }Vielleicht ist der gedanke ja hilfreich. Ich kann's nicht wissen, da ich keine Ahnung habe, wozu Du die Jobs überhaupt zwischenspeichern mußt, statt sie sofort zu erledigen. Und nach welchen Kriterien der Jobmanager sich den nächsten zu startenden Job aussucht. std::map<int, JobBase> m_jobs könnte auf Prioritäten oder auf zeitliche Abfolge hindeuten.
-
Danke volkard, dass Du Dir die Mühe machst!

Der JobManager besitzt eine Netzwerkverbindung. Die Jobs müssen alle über diese Netzwerkverbindung kommunizieren. Der JobManager sorgt dafür, dass nicht zu viele Job-Anfragen auf einmal an den Server gehen und puffert Sie in die std::map. Um dann die Antwort vom Server den einzelnen Jobs wieder zuzuordnen, wird die Job-ID aus dem std::map verwendet. Die wird mit an den Server geschickt und bei dem verwendten Protokoll vom Server einfach in die Antwort kopiert.
Sobald die Antwort des Servers dem Job zugeordnet wurde und der Job diese dekodiert hat soll er dem Aufrufer melden, dass er fertig ist (oder etwas schief gelaufen ist) und ggf. die Daten bereit stellen.
-
Ich denke schon, dass man das ganz gut über das Observer Pattern hinbekommt, dazu benötigt die Job Basisklasse allerdings etwas mehr Funktionalität.
class JobBase { // hier sowas wie boost::signal benutzen EventPublisher ProgressPublisher_; EventPublisher StatusPublisher_; public : enum JobStatus { jsSuccess = 0, jsFailure = 1 } public: virtual ~JobBase() { } EventPublisher& get_progress_publisher() { return ProgressPublisher_; } EventPublisher& get_status_publisher() { return StatusPublisher_; } protected: void fire_progress( double PercentageCompleted ) const { ProgressPublisher_.fire( *this, PercentageCompleted ); } void fire_status( JobStatus Status ) const { StatusPublisher_.fire( *this, Status ); } }; class ConcreteJob : public JobBase { public: void doJob() { while( !done ) { // Job macht irgendwas ... // und benachrichtig Observer über Fortschritt fire_progress( ... ); } // Job ist fertig fire_status( jsSuccess ); } }; class Manager { public: void add_job( JobBase* Job ) { if( Job ) { enqueue_job( Job ); Job->get_progress_publisher().subscribe( on_job_progress ); Job->get_status_publisher().subscribe( on_job_completed ); } } void on_job_progress( JobBase& Job, double PercentageCompleted ) { // mach was } void on_job_completed( JobBase& Job, JobBase::JobState State ) { // mach was // Achtung! // Wenn dieser Handler nicht der letzte Handler der Handlerliste ist // ist das Job Objekt für den nächsten Handler möglicherweise nicht // mehr gültig (Referenzzähler wird 0 beim Löschen aus der Map) remove_job( Job ); } };So, und nun kommen wir zum spannenden Teil der Geschichte:
Du musst natürlich sicherstellen, dass der Job während der Behandlung des fire_status() nicht ungültig wird, z.B. wenn du als Reaktion auf das Jobende den Job (und damit die letzte Referenz) aus der map entfernst. Eine mögliche Lösung dafür wäre, JobBase von boost::enable_shared_from_this abzuleiten und den Job selbst als Referenzzähler zu benutzen. Damit ist sichergestellt, dass solange irgendwas einen smart_ptr auf den Job hat der Referenzzähler nicht 0 wird und das Jobobjekt so lange existiert, bis es aus der Event Behandlung zurückkommt.Edit:
Der Quelltext soll nur als Beispiel dienen und ist keinesfalls kompilier- oder lauffähig. Bevor irgendein schlaues Kerlchen sich drüber beschwert...
-
volkard schrieb:
Bald ist Weihnachten. Ich darf doch annehmen, daß Du Dir Mehr effektiv C++ programmieren | ISBN: 3827312752 gewünscht hast.
Das gibt es doch alles beim oneclickhoster kostenlos solange bis du es kaufen kannst.
-
@DocShoe: Danke für Dein Tipp/Beispiel. So in der Art habe ich mir das auch gedacht, nur das eigentliche Problem besteht damit ja weiterhin:
Wie identifiziere ich den Job, der eine Rückmeldung gibt? Ist es z.B. ein Job, der mir Daten liefert, die ich nun abholen muss (abgeleitete Job-Klasse besitzt GetData()-Methode)?
-
du koenntest zum beispiel in deiner Job-basisklasse die virtuele funktion 'has_result' einfuegen.
dann kannst du die abfragen ob was zum abholen ist.Meep Meep
-
Da es ja unterschiedliche Daten gibt und es somit auch unterschiedliche Job-Klassen mit sich unterscheidenene GetData()-Methoden geben müsste (unterschiedlicher Returnwert), werde ich wohl dem konkreten Job gleich einen SmartZeiger auf die Daten mitgeben, in die der Job schreiben soll.
Damit hat sich das Daten-Abholen-Problem erledigt.
Bleibt aber die möglichst einfache Benachrichtigung des Aufrufers über Erfolg/Misserfolg. Volkards Vorschlag, wo eine Methode des Auftraggebers aufgerufen wird und eine Job-ID übergeben wird, gefällt mir gut, aber ich überlege, wie ich dann (bei mehreren unterschiedlichen Jobs) am besten raus finde, was der Job nohmal machen sollte.
Obwohl... das könnte man ja über die Job-ID erledigen... *grübel*