It really seems pretty trivial, so I guess we missed some requirements.
Use Memoization to avoid calculating the result more than once. This should be done as part of the framework.
You can use some flowchart to determine how to transfer information from one module to another ... but the easiest way is to call each module they depend on directly. With memoization, it's not that expensive, because if it has already been calculated, you're fine.
Since you need to be able to run any module, you need to provide them with identifiers and register them somewhere to see them at runtime. There are two ways to do this.
- Instance: you get a unique instance of this module and execute it.
- Factory: you create the requested module, execute it and throw it away.
The disadvantage of the Exemplar method is that if you execute a module twice, you will not start it from a clean state, but from a state in which its last (possibly not executed) execution left it. For memoization, this can be considered an advantage, but if it fails, the result is not computed (urgh), so I would recommend against it.
So how are you ...?
Let's start with the factory.
class Module; class Result; class Organizer { public: void AddModule(std::string id, const Module& module); void RemoveModule(const std::string& id); const Result* GetResult(const std::string& id) const; private: typedef std::map< std::string, std::shared_ptr<const Module> > ModulesType; typedef std::map< std::string, std::shared_ptr<const Result> > ResultsType; ModulesType mModules; mutable ResultsType mResults;
This is a very simple interface. However, since we need a new instance of the module every time we call Organizer (to avoid the re-login problem), we will need to work with our Module interface.
class Module { public: typedef std::auto_ptr<const Result> ResultPointer; virtual ~Module() {}
And now it’s easy:
// Organizer implementation const Result* Organizer::GetResult(const std::string& id) { ResultsType::const_iterator res = mResults.find(id); // Memoized ? if (res != mResults.end()) return *(it->second); // Need to compute it // Look module up ModulesType::const_iterator mod = mModules.find(id); if (mod != mModules.end()) return 0; // Create a throw away clone std::auto_ptr<Module> module(it->second->Clone()); // Compute std::shared_ptr<const Result> result(module->Execute(*this).release()); if (!result.get()) return 0; // Store result as part of the Memoization thingy mResults[id] = result; return result.get(); }
And a simple Module / Result example:
struct FooResult: Result { FooResult(int r): mResult(r) {} int mResult; }; struct FooModule: Module { virtual FooModule* Clone() const { return new FooModule(*this); } virtual ResultPointer Execute(const Organizer& organizer) {
And from the main:
#include "project/organizer.h" #include "project/foo.h" #include "project/bar.h" int main(int argc, char* argv[]) { Organizer org; org.AddModule("FooModule", FooModule()); org.AddModule("BarModule", BarModule()); for (int i = 1; i < argc; ++i) { const Result* result = org.GetResult(argv[i]); if (result) result->print(); else std::cout << "Error while playing: " << argv[i] << "\n"; } return 0; }