LP on .NET

April 3, 2013

Dependency Injection – A Compromise

Filed under: .NET,C#,Software Development — Larry Parker @ 9:38 pm

I’ve blogged a lot over the past few years about writing testable code, as well as using stubs and mocking frameworks in unit tests to test all that testable code.

The common denominator to all this has been dependency injection.  To quote myself from a previous post:

Dependency injection is a pattern whereby dependencies are supplied (or injected) into a system. … in a nutshell you simply hand the dependencies (typically as interfaces) to your class or method which then works with them in an abstract way.

I also said this in another post:

It seems that I can’t say enough about dependency injection these days. It gives me an incredible amount of control over my code in the form of testability and modularity.

I’ve been using this pattern for a couple of years now.  It really has given me an incredible amount of control over my code, especially in the form of testability.  I love writing unit tests that can mock out some or all of the injected interfaces.  It really makes testing simple.

But incredible control in the form of modularity?  When’s the last time I swapped out my persistence layer with a new module that provided a new and improved persistence layer?  Or swapped out a configuration manager with a better one by simply editing an app.config setting that pointed to another assembly?

Let me think.

Still thinking…

Hmmm.  Haven’t done it.  Except perhaps as a proof-of-concept and maybe a demo or two.

As for “simply handing the dependencies” to my classes, it’s simple in theory.  Until you end up with a constructor that takes a whole bunch of parameters and you end up writing code like this just to create an instance of your class:

   1: WidgetProvisioningManager widgetProvisioningManager = new WidgetProvisioningManager(

   2:     new WidgetProvisioningDataManager(systemContext),

   3:     new WidgetDbProvisioningDataManager(databaseServer),

   4:     o => new OrganizationDataManager(o),

   5:     (u, c) => new EmployeeDataManager(u, c),

   6:     emailServerConfiguration,

   7:     (m, t) => QueueManager.Enqueue(m, t, null),

   8:     executionContext.LogWriter,

   9:     new WidgetMembershipManager());

So much for the simple part.  Try showing this to another developer who needs to actually work with your WidgetProvisioningManager class.  “Yes Joe, to create a new instance of my WidgetProvisioningManager class, you do need to pass in seven parameters to the constructor.”  (pause)  “You got a problem with that?”

There are other options, like inversion of control containers such as StructureMap.  I’ve looked at these a bit but have not been able to warm up to them.  Too much up-front configuration, and your application becomes too dependent on them at runtime (whereas something like Moq never leaves the lab).

So now what?

Without getting into all of the heated discussions about which pattern is best (and I have encountered many both on the web and in the office), may I suggest a compromise.

Make your class’s public constructor as simple as possible, but no simpler (Einstein would love your constructor).  And developers who use your classes will not complain that they don’t have to pass in seven parameters.

As for testability, dependency injection still plays a part, as do mocking frameworks.  But make the constructor with the seven parameters internal so nobody else has to see it (and make your assembly’s internals available only to your unit test assemblies).

Using this approach, we can have our cake and eat it too.  Our WidgetProvisioningManager class now has two constructors.  The easy one marked as public for everyone to use:

   1: public WidgetProvisioningManager()

   2: {

   3:     _widgetProvisioningDataManager = new WidgetProvisioningDataManager(AppRuntime.SystemContext);

   4:     _widgetDbProvisioningDataManager = new WidgetDbProvisioningDataManager(AppRuntime.DatabaseServer);

   5:     _organizationDataManagerFactory = o => new OrganizationDataManager(o);

   6:     _employeeDataManagerFactory = (u, c) => new EmployeeDataManager(u, c);

   7:     _emailServerConfiguration = AppRuntime.EmailServerConfiguration;

   8:     _enqueueFunc = (m, t) => QueueManager.Enqueue(m, t, null);

   9:     _logWriter = AppRuntime.LogWriter;

  10:     _widgetMembershipManager =  new WidgetMembershipManager();

  11: }

And the DI constructor marked as internal for your unit tests:

   1: internal WidgetProvisioningManager(IWidgetProvisioningDataManager widgetProvisioningDataManager = null,

   2:     WidgetDbProvisioningDataManager widgetDbProvisioningDataManager = null,

   3:     Func<IContext, IOrganizationDataManager> organizationDataManagerFactory = null,

   4:     Func<IContext, EmailServerConfiguration, IEmployeeDataManager> employeeDataManagerFactory = null,

   5:     EmailServerConfiguration emailServerConfiguration = null,

   6:     Func<Int32, String, Int64> enqueueFunc = null,

   7:     ILogWriter logWriter = null,

   8:     IWidgetMembershipManager widgetMembershipManager = null)

   9: {

  10:     _widgetProvisioningDataManager = widgetProvisioningDataManager;

  11:     _widgetDbProvisioningDataManager = widgetDbProvisioningDataManager;

  12:     _organizationDataManagerFactory = organizationDataManagerFactory;

  13:     _employeeDataManagerFactory = employeeDataManagerFactory;

  14:     _emailServerConfiguration = emailServerConfiguration;

  15:     _enqueueFunc = enqueueFunc;

  16:     _logWriter = logWriter;

  17:     _widgetMembershipManager = widgetMembershipManager;

  18: }

Note that both constructors save things to private fields (the ones with the underscore prefix), and the rest of the class just uses interfaces and doesn’t care where they came from.

The public constructor creates concrete classes so consumers of the class can just use your class without any headaches.

The internal constructor is the one with all the (optional) parameters, so you can still go to town using mocking frameworks for your unit tests.

So that’s my dependency injection compromise.

Hope this helps.

Advertisements

Create a free website or blog at WordPress.com.