|
The discussion boils down to a question of semantics, just as it did with the discussion about Func<T> vs. Factory<T>. I'm firmly on the side that favor Func<T>, and I would presume that you are in the other camp. I do understand the arguments
put forth regarding semantics, but I just don't find them compelling. Eventually, it's a subjective evaluation, so I completely understand that opposing opinions thrive :)
For the record, I find Lazy<T> a Leaky Abstraction as well - when it comes to DI: I have no problem with the type itself, I just don't think it is proper to represent a dependency in this way.
My viewpoint is that DI is not a goal in itself. It's just a means to achieve loosely coupled code. This means that my personal philosophy is that code should be written to be
DI-friendly while still being completely DI-agnostic.
According to that philosophy, your example has several issues:
- It explicitly uses a particular DI Container by applying an attribute.
- It explictly uses weakly typed metadata (a string) to determine which implementation it requests. How can a named instance relate to the Domain you are trying to model? It very rarely does.
- Properties are considered optional, so the combined contract seems redundant or vague. I don't have to assign SomeService at all, but even if I do, it may not be available? How does that differ from the scenario where I don't assign the dependency at all?
As an overall principle I salute explicitness as a good thing, but I find explicitness about dependency lifetime to be wrong because it creates ambiguity about who controls the lifetime of injected dependencies.
With relation to DI, the only thing that explicitness accomplishes is to announce to the world that a DI Container is expected to be in use. I consider this detrimental instead of beneficial.
One of the current trends related to software development is a move towards a more declarative model - we would rather express
what than how. This is one of the key benefits of LINQ to objects, and one derived benefit is that it makes migration to parallelism possible with PLINQ - exactly because of the (more) declarative nature of LINQ.
I think that DI should be treated the same way. I can use Constructor Injection to statically declare that I require a certain dependency. The more explicit I become, the less declarative. Each time I explicitly state something, I remove
a degree of freedom.
Getting back to Future<T>, my point is that you don't have to explicitly state that a dependency may become available in the future. The only thing this accomplishes is to add complexity in the wrong place. Let's look at an example where
the dependency returns a value (and assuming that we don't want to use IEnumerable<T>):
string foo = "bar";
if (this.dep.IsAvailable)
{
foo = this.dep.Value.GetFoo();
}
Notice how you need to explicitly define default values to avoid null references. It would be much better if you could just write:
string foo = this.dep.GetFoo();
But what if the dependency isn't available yet? We can deal with that issue
if it ever becomes relevant, but it's a Leaky Abstraction to let the consumer deal with this issue. It's also a violation of the Single Responsibility Principle. A much more elegant way to deal with this situation is to wrap the dependency
in a lazy-loading implementation that uses a Null Object as a stand-in until the 'real' dependency becomes available.
This might look something like this:
public class NullOrFutureDep : IDep
{
public string GetFoo()
{
IDep realDep = this.PollRealDep();
if(realDep != null)
{
return realDep.GetFoo();
}
return "bar";
}
private IDep PollRealDep()
{
// implement availability mechanism here
}
}
We have now cleanly separated the responsibility of managing the dependency from consuming it, but the best part is that we have deferred the decision until the
Last Responsibile Moment.
It would be cool to have a type that could automatically emit a Future implementation like the above. I suspect this would be something that could be implemented fairly simply with Castle DynamicProxy, but now I'm just guessing...
|