Configuration

Alan Griffiths alan.griffiths at canonical.com
Tue Apr 16 14:28:48 UTC 2013


We've agreed that the DefaultServerConfiguration is becoming an unwieldy
mess. (At least racarr and I have.)

Before coding anything up I want to propose a few principles to see if
there's any agreement.

With a few exceptions the structural classes that are build by
DefaultServerConfiguration take explicit shared pointers to their
dependencies. This approach makes dependencies explicit - which, for
example, makes use of test doubles clear when testing.

The exceptions are DisplayServer (which takes ServerConfiguration by
reference) and InputManager & DispatcherController (which take
InputConfiguration by shared_ptr). There's also an MP under review that
has SessionManager take ShellConfiguration by shared_ptr. (I think the
use of shared_ptr in these three exceptions is misleading but, if we
agree that concealing dependencies line this is bad, the shared_ptr is
probably moot.)

My concern with this latter approach is that it couples the dependencies
of the class to an interface - and makes reuse of mir by supplying
alternative implementations a little harder - as it is no longer always
a matter of overriding a factory method in DefaultServerConfiguration.

However, I've thought myself around in a circle trying to improve
things. (Sharing because someone out there will contribute the insight
I'm lacking.)

~~~~

What I'd like to see is that these inter-dependencies are built up
within the ServerConfiguration class hierarchy in a way that allows
alternative implementations to be substituted.

What I'm not sure is possible (without exposing lots of implementation
as template code) is how flexible such a solution is.

E.g. (Approach #1)

class ServerConfiguration { /* interface supplying dependencies of
DisplayServer */ };

class DefaultSubsystem1Dependencies : public virtual ServerConfiguration
  { /* interface supplying dependencies of Subsystem1 */ };
...
class DefaultSubsystem5Dependencies : public virtual ServerConfiguration
  { /* interface supplying dependencies of Subsystem5 */ };

class DefaultDependencies :
    public virtual DefaultSubsystem1Dependencies,
    ...
    public virtual DefaultSubsystem5Dependencies
{ /* all the dependencies */ };

class DefaultSubsystem1 : public virtual ServerConfiguration, virtual
DefaultDependencies
  { /* implementation building Subsystem1 */ };
...
class DefaultSubsystem5 : public virtual ServerConfiguration, virtual
DefaultDependencies
  { /* implementation building Subsystem5 */ };

class DefaultServerConfiguration :
    public virtual ServerConfiguration,
    virtual DefaultSubsystem1,
    ...
    virtual DefaultSubsystem5
{ /* combines the implementations */ };

The inflexibility exists because the DefaultSubsystemX classes have to
derive from any DefaultSubsystemY interfaces that contain functions they
implement. Some flexibility could be attained using typelists, but not
without making the implementation classes templates. (Which pulls all
the implementation knowledge into a header - even worse than the current
all the implementation knowledge into a translation unit.)

Another issue with this approach is that there is nothing keeping the
dependencies in DefaultSubsystem1Dependencies correct (as long as a
needed function is declared in some interface everything compiles).

So, with this approach, there seems to be little value in the
DefaultSubsystemXDependencies interfaces - everything could be placed
directly into DefaultDependencies. Hence "Approach #2"...

~~~~

Approach #2

class ServerConfiguration { /* interface supplying dependencies of
DisplayServer */ };

class DefaultDependencies : public  virtual ServerConfiguration
{ /* all the dependencies */ };

class DefaultSubsystem1 : public virtual ServerConfiguration, virtual
DefaultDependencies
  { /* implementation building Subsystem1 */ };
...
class DefaultSubsystem5 : public virtual ServerConfiguration, virtual
DefaultDependencies
  { /* implementation building Subsystem5 */ };

class DefaultServerConfiguration :
    public virtual ServerConfiguration,
    virtual DefaultSubsystem1,
    ...
    virtual DefaultSubsystem5
{ /* combines the implementations */ };

Now this allows someone to come along with a:

class NonDefaultSubsystem3 : public virtual ServerConfiguration, virtual
DefaultDependencies
  { /* implementation building Subsystem3 */ };

class NonDefaultServerConfiguration :
    public virtual ServerConfiguration,
    virtual DefaultSubsystem1,
    ...
    NonDefaultSubsystem3,
    ...
    virtual DefaultSubsystem5
{ /* combines the implementations */ };

But this doesn't gain much over simply splitting up the implementation
file. (Approach #3)

~~~~

class ServerConfiguration { /* interface supplying dependencies of
DisplayServer */ };

class DefaultServerConfiguration :
    public ServerConfiguration
{ /* declare the factory functions */ };

// implentation file 1
DefaultServerConfiguration methods to build Subsystem1
...
// implentation file 5
DefaultServerConfiguration methods to build Subsystem5

And someone can come along with a:

class NonDefaultServerConfiguration :
    public DefaultServerConfiguration
{ /* override some of the factory functions */ };

And apart from splitting up a monster .cpp we're back where we started.
(Splitting up the implementation file isn't a bad idea though.)



More information about the Mir-devel mailing list