This noon I had an interesting discussion with some colleagues about my way of dependency injection.
As good design dictates, I always strive for decoupling of definition and implementation using interfaces. (As a bonus, and it makes unit testing using Moq or Rhino Mocks possible without have to resort to TypeMock). But when an IoC cannot be is not allowed to be used, dependencies have to be set using Constructor Injection or Setter Injection. IMHO, both approaches have drawbacks.
Imagine the following layered application :
1: public class MyDataClass
2: {
3: public string SomeData { get; set;}
4: }
5:
6: public class MyDataAccessLayerClass
7: {
8: public MyDataClass GetSomeData()
9: {
10: return new MyDataClass { SomeData = "some data" };
11: }
12: }
13:
14: public class MyBusinessLayerClass
15: {
16: public void DoSomeBusinessStuff()
17: {
18: MyDataAccessLayerClass dao = new MyDataAccessLayerClass();
19: MyDataClass mc = dao.GetSomeData();
20: //do business stuff
21: }
22: }
Off course this is bad, because there is no decoupling at all. The datalayer component is blatantly instantiated within the business method.
So let's add some decoupling by refactoring to constructor injection. We end up with :
1: public class MyDataClass
2: {
3: public string SomeData { get; set; }
4: }
5:
6: public interface IMyDataAccessLayerClass
7: {
8: MyDataClass GetSomeData();
9: }
10:
11: public class MyDataAccessLayerClass : IMyDataAccessLayerClass
12: {
13: public MyDataClass GetSomeData()
14: {
15: return new MyDataClass { SomeData = "some data" };
16: }
17: }
18:
19: public class MyBusinessLayerClass
20: {
21: private IMyDataAccessLayerClass dao;
22:
23: public MyBusinessLayerClass()
24: : this(new MyDataAccessLayerClass())
25: { }
26:
27: internal MyBusinessLayerClass(IMyDataAccessLayerClass myDataAccessLayerClass)
28: {
29: dao = myDataAccessLayerClass;
30: }
31:
32: public void DoSomeBusinessStuff()
33: {
34: MyDataClass mc = dao.GetSomeData();
35: //do business stuff
36: }
37: }
Perfect ! Nice loose coupling and possibility to inject mocks in our unit testing. But when instantiating the business object using the default constructor, a data-accesslayer object get also instantiated. And when using heavy layered applications, this technique will possible construct a whole tree structure of object instances throughout all layers. It could be just me, but that smells bad.
On the other hand, pure setter injection has the drawback that you always have to remember to set the dependency yourself before using methods on the object that could use the dependency.
My solution : "hybrid dependency injection". Let's refactor our businesslayer code to :
1: public class MyBusinessLayerClass
2: {
3: private object _dao = null;
4: private IMyDataAccessLayerClass dao
5: {
6: get
7: {
8: if (_dao == null)
9: _dao = new MyDataAccessLayerClass();
10: return (IMyDataAccessLayerClass)_dao;
11: }
12: }
13:
14: public MyBusinessLayerClass() { }
15:
16: internal MyBusinessLayerClass(IMyDataAccessLayerClass myDataAccessLayerClass)
17: {
18: _dao = myDataAccessLayerClass;
19: }
20:
21: public void DoSomeBusinessStuff()
22: {
23: MyDataClass mc = dao.GetSomeData();
24: //do business stuff
25: }
26: }
So what we have now is constructor injection possibilities for unit testing & mocking, but in "real usage", the default constructor of the business class doesn't need to instantiate the data access class anymore.
Notice the declaration of _dao on line 3, it is of type 'object' and not of type 'IMyDataAccessLayerClass'. This is to prevent the methods of getting away with using the _dao instance instead of the dao property (causing runtime errors) , and sneaking through the unit tests !
Now the discussion this noon was that one colleague told me that I was actually doing 'premature optimization', arguing that the system was probably not going to contain a zillion layers, so that constructor injection was not going to yield a huge object structure when instantiating objects.
Being a firm believer of the KISS and YAGNI principles, I want to react that it's the other way around, the constructor injection is the premature one ! Why instantiate the dependency at constructor time, you might not need it after all ?
Anyway, the hybrid injection works really nice for me, although for more complex projects, I'd definitely go for an IoC ! But that's another story ...