I have a bit of code which uses a plugin and core model, revolving around four major objects: the Core
, Server
, Logger
, and Parser
, in slightly particular order.
The Core
acts as the central factory, with a catch: it's bound to a single render context (much of this is graphics related, but the other details are not important). The objects it creates and manages are specific to that core, and multiple cores can be created with different contexts. Cores can never share resources with each other. The Core
is initialized from a configuration file; more on this later. The Core
must have, from its creation, a Server
, Parser
, and Logger
.
The Server
handles loading and initializing all the plugins, as well as creating objects from them. It acts as a single, in-process plugin server and object registry, similar in many ways to a STA in-process COM server. As plugins are loaded into the process, not a particular Core
, I'm fairly sure this is appropriate. The Server
must have a Parser
and Logger
.
The Logger
handles logging messages, particularly errors, to disk. It does very simple pre-formatted file writing, with some basic "log level" filtering. The Logger
requires a Parser
for resolving file paths.
The Parser
handles variable replacement, particularly in paths. For example, a plugin may request the file $(root)/resources/a.txt
and the Parser
will replace that with the application's root directory; or $(startup)/a.txt
for the startup directory. Simple stuff. Variables are contained in the Parser
itself, and many are specific to the thread. However, a few are tied to a single Core
, and thus enters the...
Config file. Each Core
is initialized from a config file, which contains a multitude of settings: plugins for the Server
, variables for the Parser
, and a file for the Logger
. Each Core
can use a different config file, and most likely will.
In my current model, the Server
, Logger
, and Parser
are singletons. They all must provide a single global instance, accessible from the core and plugins (and each other), potentially before a Core
has been created. All handle process-level functions, but the Logger
and Parser
do handle some Core
-level functionality.
The issue with this is that, in order to make the Logger
and/or Parser
Core
-specific, the Server
would then need its own instances, which would be potentially lacking important characteristics (like what file to use for the Logger
). The Server
also handles work that is very clearly common to the whole process: there's just no way to load a plugin into a single Core
, that's not how shared libraries work.
In a previous version, I had all four of these created when a Core
was created, and belong to a specific Core
. This caused some issues and oddities, however: the Core
loads itself as a "plugin" when it starts up, but the Server
had the same lifetime as the Core
, making this seem odd. The Parser
has a number of variables that belong to the process: the initial directory, root directory, and so on. The Logger
must precede the Core
in some way, particularly the plugin-loading stage, so errors can be accurately logged.
My questions then are:
- Is this setup too over-engineered? The separation of responsibility between objects seems good, but perhaps too split.
- What design pattern(s), be it singleton or otherwise, is/are appropriate in this case, for these objects?