In creating an application I tend to have a central object that acts as a main hub to control sub-object lifetimes and provide object services.
Code:
class ISaveLoad
{
public:virtual void save( IApp *pHub, char *fileName, char *pdata, uint32 size ) =0 { ... }
int32 countFiles(char *folderName) { ... }
...
};
class IApp
{
public:virtual ~IApp() {}
virtual void startup()=0 {}
virtual void shutdown()=0 {}
...
ISaveLoad *m_pfileMgr;
};
class SaveLoad : public ISaveLoad
{
public:void save( IApp *pHub, char *fileName, char *pdata, uint32 size ) { ISave::save(pHub,fileName,pdata); ... }
};
class MyApp : public IApp
{
public:MyApp() : IApp()
{
m_pFileMgr = new SaveLoad();
}
static MyApp &getInstance()
{
if( !s_pInstance )
s_pInstance = new MyApp();
}
virtual void startup()
{
IApp::startup();
...
}
virtual void shutdown() { ... IApp::shutdown(); }
private:static MyApp *s_pInstance;
};
Now the interface IApp will contain generic non-app specific objects like saving, loading, sound, screen res, registry access, etc, etc. Some of these objects will need to call on each other so will often need to know about base part of MyApp. They'll include IApp.h and need an IApp pointer. Some of them will get overridden by MyApp and contain low level generic elements, but also high level app specific elements.
The application MyApp will contain app-specific code to layout menu screens and do actual work, etc. Again these objects will need to call on each other occasionally and more often the objects of the base. So they'll include MyApp.h sometimes and IApp.h often and need a MyApp/IApp pointer.
To service the application's need for MyApp access I make MyApp a singleton and then any app-specific code can include the appropriate headers and call MyApp::getInstance(). The aim is to avoid that call where possible and instead favour using the base interface. The theory being that it's ok for app level code to use the services of the base and therefore become dependant on it as at the base level the code should all be generic. App level code becoming dependant on app level objects is not good as that starts increase coupling to non-generic objects.
I'm not sure I've explained it very well, but it seems like there are two layers to an application: a generic base layer and an app specific higher layer. This happens even within a single object, some low level non-virtual parts, some virtual high level parts. Patterns emerge where the low level objects and object parts tend to couple with each other. Whereas the higher level objects and object parts tend to couple with the lower level. That kind of coupling
seems i.e. low -> low and high -> low and it's only when high -> high occurs that you get a lot of problems. Obviously any coupling should be minimised, I'm just talking in general terms.
Initially I found I was constantly passing MyApp pointers into the low level generic objects as they don't have access to the MyApp singleton so can't get at the hub. This seems wrong, extra paramteres on every function. At the base level I'd like to go IApp::getInstance() but I don't think that can be done simply.
Here's one idea, but it worries me.
Code:
class IApp
{
public:IApp()
{
assert(!s_pBaseInstance);
s_pBaseInstance = this;
}
static IApp &getBaseInstance()
{
assert(s_pBaseInstance);
return s_pBaseInstance;
}
private:static IApp *s_pBaseInstance;
};
So now app level code can call MyApp::getInstance() if it needs to couple to other objects and low level generic code can call IApp::getInstance(). If I make sure low level generic code never calls MyApp then it never needs to know about MyApp or include any header files from that higher level.
The obvious problem is if you had two Apps in the same execution each inheriting from IApp. That would be a disaster, but that should never happen - one execution == one app.
Is there a better way of doing this or is this ok? Maybe this is all systematic of relying on inheritance and polymorphism, I know people are starting to say we should avoid inheritance all together and favour object composition instead. Maybe my whole approach is wrong :)