jordan.terrell
Just trying to make sense of things...

You're All Applying the Dependency Inversion Principle

Monday, 27 August 2007 11:12 by jordan.terrell

John has posted a few comments on my previous post about the Dependency Inversion Principle (DIP) debate.  He raised a few questions and the more I thought about it, I felt it warranted a follow up post.

First, when it come to adding support for change ahead of the change, balance is always needed.  No one can future proof their software solutions against change - you'll end up with a ton of infrastructure code that only adds complexity and very little of the value originally intended.  However, change will happen.  Adding support of that at the junction points between tiers/layers is the responsible thing to do in all but the smallest of applications.  Again, balance is always needed - it can be easy to go to far with this.

Next, to answer John's specific questions: "It is the price of the tool that you are worried about?  The commercial support?"  Answer: Yes and no.  The price point is always a concern, as licensing is usually based on the number of computers that it will be installed on.  I appreciate having the right tools for each project, but sometimes the project's budget doesn't include monies for licenses to developer tools - besides Visual Studio.  This is not to say that I'm completely apposed to commercial tools - I'm a large fan of CodeSmith Professional, and I have recommended its use on many projects.    The thing about CodeSmith, however, is that its use doesn't affect the design characteristics of my application.  I can choose to apply the DIP or not, all the while using CodeSmith.  That is the point that I'm getting at - I don't want the selection of a development tool¹ to drive how certain design principles (e.g. Dependency Inversion, Single Responsibility, Open-Closed, Loose Coupling, High Cohesion) are achieved.  John and others have suggested that TypeMock could be used to achieve certain desirable design characteristics.  Take TypeMock away, and I fear in most cases (not all) that you would be left with an non-testable, highly-coupled design.

It has been suggested by those opposed to the DIP in this debate that the only reason developers are using the DIP is because of the use of mocking frameworks in their unit test.  I can see how that perception could easily be arrived at.  When applying the DIP, it provides many testing seams that can be leveraged by a mocking framework.  Let's stop for a second and ask, why is that?  It is because applying the DIP allows one to achieve a measure of loose-coupling.  But with all principles and patterns, a balance is needed.  I do not try to make all class-to-class interactions happen through interfaces; that would be unreasonable.  More often I use it when bridging the connection between tiers/layers of an application (e.g. business layer to data access layer).

You're Applying The Dependency Inversion Principle - You Just Might Not Know It

One of the wonderful things about this field of work is that there are many numerous ways that a principle or pattern can be applied.  There are many ways to arrive at a software solution for a problem - not always right or wrong, but better or worse.  It seems in all of this discussion about using DIP that it somehow became synonymous with using interfaces passed in via constructors, methods, or properties.  This is an incorrect viewpoint.  There is no requirement in the DIP that says interfaces be used, or that it requires the use of constructors, methods, or properties.  Principles can be adhered to many different ways. Dependency Inversion is just that - a principle - not a concrete implementation approach.

Jacob Proffitt said that he would prefer to use providers (abstract factories) instead of DI.  Great - it's still applying the Dependency Inversion Principle!  The Inversion part comes in you use some form of configuration to determine which concrete implementation of the provider will be used at runtime.  You can always implement something into your solution which lets you intercept the request for the provider and return a mocked version - that is if your provider framework allows this.

A Different Approach

I had hinted in a previous post on an approach that I've used in the past based on the Scope<T> MSDN sample.  I whipped² together a sample of this approach for you to take a look at.

First, there is a simple static class that provides simple and clean access to retrieving dependencies (IoC stands for Inversion of Control):

   1:      public static class IoC
   2:      {
   3:          public static T Resolve<T>()
   4:          {
   5:              return Resolve<T>(null);
   6:          }
   7:   
   8:          public static T Resolve<T>(string name)
   9:          {
  10:              return IoCScope.Current.Resolve<T>(name);
  11:          }
  12:   
  13:          public static IoCScope Register<T>(T target)
  14:          {
  15:              return Register<T>(target, null);
  16:          }
  17:   
  18:          public static IoCScope Register<T>(T target, string name)
  19:          {
  20:              return IoCScope.Current.Register<T>(target, name);
  21:          }
  22:      }

 

Next, we have an abstract base class that represents an individual IoCScope, and well as manages the stack of nested IoCScopes.  One nice thing about this implementation is that it allows scopes to be defined for an entire AppDomain, or for just one thread:

   1:      public abstract class IoCScope : IDisposable
   2:      {
   3:          private static Stack<IoCScope> _AppDomainScopes = null;
   4:          
   5:          [ThreadStatic]
   6:          private static Stack<IoCScope> _ThreadScopes = null;
   7:   
   8:          internal static IoCScope Current
   9:          {
  10:              get { return GetCurrentScope(); }
  11:          }
  12:   
  13:          private static IoCScope GetCurrentScope()
  14:          {
  15:              if (_ThreadScopes != null && _ThreadScopes.Count > 0)
  16:                  return _ThreadScopes.Peek();
  17:   
  18:              if (_AppDomainScopes != null && _AppDomainScopes.Count > 0)
  19:                  return _AppDomainScopes.Peek();
  20:   
  21:              return null;
  22:          }
  23:   
  24:          private bool _IsAtAppDomainScope = false;
  25:   
  26:          public IoCScope() : this(false)
  27:          {
  28:          }
  29:   
  30:          public IoCScope(bool appDomainScope)
  31:          {
  32:              _IsAtAppDomainScope = appDomainScope;
  33:   
  34:              Stack<IoCScope> scopeStack = GetScopeStack(_IsAtAppDomainScope);
  35:              scopeStack.Push(this);
  36:          }
  37:   
  38:          private Stack<IoCScope> GetScopeStack(bool isAtAppDomainScope)
  39:          {
  40:              if (isAtAppDomainScope)
  41:                  return _AppDomainScopes ?? (_AppDomainScopes = new Stack<IoCScope>());
  42:              else
  43:                  return _ThreadScopes ?? (_ThreadScopes = new Stack<IoCScope>());
  44:          }
  45:          
  46:          public void Dispose()
  47:          {
  48:              if (_IsAtAppDomainScope)
  49:                  TearDownScope(_AppDomainScopes);
  50:              else
  51:                  TearDownScope(_ThreadScopes);
  52:          }
  53:   
  54:          private void TearDownScope(Stack<IoCScope> scopeStack)
  55:          {
  56:              if (scopeStack.Peek() != this)
  57:                  throw new ApplicationException("IoCScope disposed in wrong order.");
  58:   
  59:              scopeStack.Pop();
  60:          }
  61:          
  62:          public abstract T Resolve<T>(string name);
  63:          protected abstract void RegisterCore<T>(T target, string name);
  64:   
  65:          public IoCScope Register<T>(T target)
  66:          {
  67:              RegisterCore<T>(target, null);
  68:              return this;
  69:          }
  70:   
  71:          public IoCScope Register<T>(T target, string name)
  72:          {
  73:              RegisterCore<T>(target, name);
  74:              return this;
  75:          }
  76:      }

 

IoCScope is an abstract class because it allows you to implement registration and resolution of dependencies using your favorite IoC framework, or to write your own.  Assuming you've created one called MyScope, you can wire up dependencies via code this way:

   1:  using(MyScope scope = new MyScope())
   2:  {
   3:      scope.Register<ISomeService>(new SomeConcreteService());
   4:      ...
   5:  }

 

This registers an instance of SomeConcreteService with the current scope.  In can now be retrieved this way:

   1:  ISomeService svc = IoC.Resolve<ISomeService>();
   2:  svc.DoSomeWork();

 

Within the context of a unit test, you can define a new scope and register mocked instances of services that your component under test depends on.

Conclusion

I guess my overall message is this: You're all applying the DIP, just in different ways, so stop attacking it!  If you dislike the concrete approach that a large group of developers are using, let's talk about that.

 

Nitpicker's Corner

¹I'm not referring to the .NET Framework or 3rd party libraries you would add a reference to - naturally those should have an effect on your design.  I'm speaking of tools used by the developer in the process of writing the software's code.

²So don't expect a feature complete, error-hardened library.  If you choose to use this code in anyway, you accept that I am not responsible for what results, directly or indirectly.  If this sample seems rushed - it was.  If you'd like to have me send you a working Visual Studio 2005 project, request it via a comment on this post and I will send it your way.

Comments (7) -

August 28. 2007 09:05

John

Jordan
Using the wrong tools will effect your design -> You MUST use DI.
Using the right tools will allow YOU to decide what your design will look like -> You can use ANY design

I understand that you are joining in on all the fun, but please try to be a free thinker.
Are you really going to need to inject anything but mock objects????
If not - your tools made your design too complex, with additional API's and additional Interfaces (YAGNI) and less encapsulated (i.e. everyone knows about all your objects)

If you want to call MessageBox.Show() - are you sure that it is worth all the time spent on hiding this in another method that has an interface that can be injected into the code?

DI is NOT a design principle - It is a hard sell for tools that just couldn't solve the isolation problem in any other way.

Here is a conversation that shows how this came to be:
Developer: "Can I mock this?"
- DI'er: "oh you want to mock this - you will have to give us an interface"

Developer: "But I don't have an interface"
- DI'er: "Make one, and make sure that you don't create the object in your code, and make sure that there is a way to inject another type"

Developer: "All I wanted is to test my code"
- DI'er: "Don't worry - this makes your code better - It is loosly coupled - Hey using this technique will make your code better"

Developer: "But the I loose encapsulation, I don't want other object to know about this interface"
- DI'er: "Oh, everyone cool is using this technique, forget about encapsulation, it is overrated anyway. We are now going to change TDD from Test Driven Development to Test Driven Design! Because it doesn't work with all designs - you HAVE to confirm with our design - but don't worry you get better *cough* code"

John

August 28. 2007 13:34

David Meyer

"Are you really going to need to inject anything but mock objects????"

Um, yeah.  Case in point: I wrote a simple data access library which defines an interface called IDataSource which provides data connections to the library.  Thanks to dependency injection, all you have to do to add support for another data source is define one class with a few simple methods, which can be done seperate from the general library.  So I can use this data access library with SQL Server, MySql, Oracle, you name it, without modification.  Testing by injecting a mock object never even crossed my mind when I designed this.  DI is NOT just for testing.

I agree, though, that an interface should not be created and exposed for every internal function just to make testing easier.  The ability to inject mock objects is a fortunate advantage of DI, but it is not its purpose.

David Meyer

August 28. 2007 13:39

Jordan

Ahhhhh...the debate rages on...

Ok John, two things I said in my post:

    "There is no requirement in the DIP that says interfaces be used..."
    "I do not try to make all class-to-class interactions happen through interfaces; that would be unreasonable."

I would NEVER slap an interface on top of MessageBox.Show() - that would be ridiculous.  I made it quite clear that I use the DIP to separate tiers of an application.

As for "everyone knows about all your objects" - that is an incorrect statement.  If I have class A that has a dependency on class B, and class C needs to use class A, class C does *not need* to know anything about class B.  That is the beauty of loosely coupled designs.  A framework, or your Main() method, can wire all the objects together and go about their way.

As for "joining in on all the fun" - your right, I do have fun developing software.  I hope everyone does.  But I also take it very serious as well.  For the newer developers, and those who have to maintain and understand my code, I strive to make my designs as simple and straightforward - which includes the use of, YES, principles.  Yes, Dependency Inversion IS a principle.  Many well-respected software developers such as Martin Fowler, Robert C. Martin, Scott Bellware, and Ian Cooper - just to name a few - with many, many years of experience have *proven* that this is a valuable principle.  If your experience has been otherwise - I am sorry for that.  Principles can be abused because of lack of understanding, experience, or ulterior motives.  Nonetheless, they can be applied with a balance, and for the betterment of a software design.

Jordan

August 28. 2007 13:40

Jordan

A related post...

blog.jordanterrell.com/.../...tatic-Languages.aspx

Jordan

August 29. 2007 04:52

John

Jordan,
DI is good and has its merits - it is a COOL pattern. But that is not our real discussion.

<b>Freedom of design is our discussion</b>

"The thing about CodeSmith, however, is that its use doesn't affect the design characteristics of my application.  I can choose to apply the DIP or not, all the while using CodeSmith."

"The thing about TypeMock, however, is that its use doesn't affect the design characteristics of my application.  I can choose to apply the DIP or not, all the while using TypeMock."

"The thing about other mocks, however, is that its use DOES affect the design characteristics of my application.  I MUST choose to apply the DIP, there is NO other choice"

There is NO way to test code that calls MessageBox.Show() without slapping an interface around it, unless you use TypeMocking.

I rest my case.

John

August 29. 2007 22:20

Nate Kohari

Great post. Somehow I missed this part of the discussion. Smile You're absolutely right that Dependency Inversion or Inversion of Control, or whatever it is that Fowler says we should call it these says (grin) goes further than Dependency Injection. Injection is just one of the many ways IoC can be implemented. It also happens to be the most popular. You're spot-on in saying that in spite of attacking it, it's used more commonly than it appears.

Nate Kohari

October 5. 2007 10:59

Dilip

This is the problem with buzzword compliance -- take perfectly common methodologies that people have been practising for ages and form a movement around it.  Dependency Injection is one such thing.  Martin Fowler is a past master at that particular form of art.  For that matter I don't even remember most of the GoF patterns.  I wouldn't know Chain of Responsibility from Texas Chainsaw Massacre.  I tend not to -- it somehow straightjackets me into a particular form of thinking.  I'd rather come up with a sensible design and see if it fits a pattern.  

BTW, that Scope<T> pattern looks alarmingly like Alexandrescu's ScopeGuard in C++.

Dilip

Comments are closed