问题
For the purpose of this discussion, there are two kinds of parameters an object constructor might take: state dependency or service dependency. Supplying a service dependency with an IOC container is easy: DI takes over. But in contrast, state dependencies are usually only known to the client. That is, the object requestor.
It turns out that having a client supply the state params through an IOC Container is quite painful. I will show several different ways to do this, all of which have big problems, and ask the community if there is another option I'm missing. Let's begin:
Before I added an IOC container to my project code, I started with a class like this:
class Foobar {
//parameters are state dependencies, not service dependencies
public Foobar(string alpha, int omega){...};
//...other stuff
}
I decide to add a Logger service depdendency to the Foobar class, which perhaps I'll provide through DI:
class Foobar {
public Foobar(string alpha, int omega, ILogger log){...};
//...other stuff
}
But then I'm also told I need to make class Foobar itself "swappable." That is, I'm required to service-locate a Foobar instance. I add a new interface into the mix:
class Foobar : IFoobar {
public Foobar(string alpha, int omega, ILogger log){...};
//...other stuff
}
When I make the service locator call, it will DI the ILogger service dependency for me. Unfortunately the same is not true of the state dependencies Alpha and Omega. Some containers offer a syntax to address this:
//Unity 2.0 pseudo-ish code:
myContainer.Resolve<IFoobar>(
new parameterOverride[] { {"alpha", "one"}, {"omega",2} }
);
I like the feature, but I don't like that it is untyped and not evident to the developer what parameters must be passed (via intellisense, etc). So I look at another solution:
//This is a "boiler plate" heavy approach!
class Foobar : IFoobar {
public Foobar (string alpha, int omega){...};
//...stuff
}
class FoobarFactory : IFoobarFactory {
public IFoobar IFoobarFactory.Create(string alpha, int omega){
return new Foobar(alpha, omega);
}
}
//fetch it...
myContainer.Resolve<IFoobarFactory>().Create("one", 2);
The above solves the type-safety and intellisense problem, but it (1) forced class Foobar to fetch an ILogger through a service locator rather than DI and (2) it requires me to make a bunch of boiler-plate (XXXFactory, IXXXFactory) for all varieties of Foobar implementations I might use. Should I decide to go with a pure service locator approach, it may not be a problem. But I still can't stand all the boiler-plate needed to make this work.
So then I try another variation of container-provided support:
//overall, this is a pseudo-memento pattern.
class Foobar : IFoobar {
public Foobar (FoobarParams parms){
this.alpha = parms.alpha;
this.omega = parms.omega;
};
//...stuff
}
class FoobarParams{
public FoobarParams(string alpha, int omega){...};
}
//fetch an instance:
FoobarParams parms = new FoobarParams("one",2);
//Unity 2.0 pseudo-code...
myContainer.resolve<IFoobar>(new parameterOverride(parms) );
With this approach, I've half-way recovered my intellisense. But I must wait till run-time to detect the error where I might forget to supply the "FoobarParams" parameter.
So let's try a new approach:
//code named "concrete creator"
class Foobar : IFoobar {
public Foobar(string alpha, int omega, ILogger log){...};
static IFoobar Create(string alpha, int omega){
//unity 2.0 pseudo-ish code. Assume a common
//service locator, or singleton holds the container...
return Container.Resolve<IFoobar>(
new parameterOverride[] {{"alpha", alpha},{"omega", omega} }
);
}
//Get my instance:
Foobar.Create("alpha",2);
I actually don't mind that I'm using the concrete "Foobar" class to create an IFoobar. It represents a base concept that I don't expect to change in my code. I also don't mind the lack of type-safety in the static "Create", because it is now encapsulated. My intellisense is working too! Any concrete instance made this way will ignore the supplied state params if they don't apply (a Unity 2.0 behavior). Perhaps a different concrete implementation "FooFoobar" might have a formal arg name mismatch, but I'm still pretty happy with it.
But the big problem with this approach is that it only works effectively with Unity 2.0 (a mismatched parameter in Structure Map will throw an exception). So it is good only if I stay with Unity. The problem is, I'm beginning to like Structure Map a lot more. So now I go onto yet another option:
class Foobar : IFoobar, IFoobarInit {
public Foobar(ILogger log){...};
public IFoobar IFoobarInit.Initialize(string alpha, int omega){
this.alpha = alpha;
this.omega = omega;
return this;
}
}
//now create it...
IFoobar foo = myContainer.resolve<IFoobarInit>().Initialize("one", 2)
Now with this I've got a somewhat nice compromise with the other approaches: (1) My arguments are type-safe / intellisense aware (2) I have a choice of fetching the ILogger via DI (shown above) or service locator, (3) there is no need to make one or more seperate concrete FoobarFactory classes (contrast with the verbose "boiler-plate" example code earlier), and (4) it reasonably upholds the principle "make interfaces easy to use correctly, and hard to use incorrectly." At least it arguably is no worse than the alternatives previously discussed.
One acceptance barrier yet remains: I also want to apply "design by contract."
Every sample I presented was intentionally favoring constructor injection (for state dependencies) because I want to preserve "invariant" support as most commonly practiced. Namely, the invariant is established when the constructor completes.
In the sample above, the invarient is not established when object construction completes. As long as I'm doing home-grown "design by contract" I could just tell developers not to test the invariant until the Initialize(...) method is called.
But more to the point, when .net 4.0 comes out I want to use its "code contract" support for design by contract. From what I read, it will not be compatible with this last approach.
Curses!
Of course it also occurs to me that my entire philosophy is off. Perhaps I'd be told that conjuring a Foobar : IFoobar via a service locator implies that it is a service - and services only have other service dependencies, they don't have state dependencies (such as the Alpha and Omega of these examples). I'm open to listening to such philosophical matters as well, but I'd also like to know what semi-authorative reference to read that would steer me down that thought path.
So now I turn it to the community. What approach should I consider that I havn't yet? Must I really believe I've exhausted my options?
p.s. This kind of problem, along with others, prompts me to believe the whole IOC Container idea, at least in .net, is premature. It reminds me of the days when people would stand on their heads to make the "C" language feel object-oriented (adding weird macros and such). What we should be looking for is CLR and language support of IOC Containers. For example, imagine a new kind of interface called an "initiate". An initiate works like an interface, but requires a particular constructor signature as well. The rest I leave as an exercise to the student...
回答1:
What you're looking for is the "factory adapter" or Func<T, U>.
A component that needs to create parameterised instances of another component on the fly uses the Func<T, U> dependency type to represent this:
class Bar
{
Func<string, int, IFoo> _fooCreator;
public Bar(Func<string, int, IFoo> fooCreator) {
_fooCreator = fooCreator;
}
public void Go() {
var foo = _fooCreator("a", 42);
// ...
}
}
Autofac, for example, will automatically provide the Func whenever IFoo is registered. It will also merge parameters into the Foo constructor (including ILogger,) and discard any that are unnecessary rather than throwing an error.
Autofac also supports custom delegates like:
delegate IFoo FooCreator(string alpha, int omega);
This way Bar can be rewritten:
class Bar
{
FooCreator _fooCreator;
public Bar(FooCreator fooCreator) {
_fooCreator = fooCreator;
}
public void Go() {
var foo = _fooCreator("a", 42);
// ...
}
}
When custom delegates are used, the parameters will be matched by name, rather than by type as for Func.
Autofac has some documentation you might check out: http://code.google.com/p/autofac/wiki/DelegateFactories.
There's a collaborative project in the works between several IOC container communities called "Common Context Adapters" to standardise these and other higher-order dependency types. The project site is at http://cca.codeplex.com.
The first proposed specification from CCA covers the factory adapter, you can read it here: http://cca.codeplex.com/wikipage?title=FuncFactoryScenarios&referringTitle=Documentation.
Some other links you may find helpful:
http://nblumhardt.com/2010/04/introducing-autofac-2-1-rtw/ http://nblumhardt.com/2010/01/the-relationship-zoo/
I hope you'll find that while IoC containers admittedly have a long way to go before they'll be completely transparent, we're actually working quite hard to get there :)
Nick
回答2:
If these parameters are constant for the lifetime of the application, then you can add a IConfigurationService whose sole purpose is to return these parameters to whoever needs them. The implementation of IConfigurationService might have hardcoded values, read them from a config file... whatever. Of course the implementation of IConfigurationService is retrieved through the IoC container.
If these parameters vary per instance, then I don't think they should be supplied as constructor parameters for objects loaded by your IoC container. This makes all your components need to find/depend on the IoC container, which defeats the point of an IoC container in the first place.
Either make them configurable on the object itself by exposing a setter method, (which is appropriate when they might change over the lifetime of the object) or make them parameters to a factory method that returns the object (where the factory object is registered in your IoC container).
You were hesitant to use a factory, but I think this is an elegant approach. Yeah it takes effort to create a factory, but since there is a requirement supporting the act, its not code bloat. Its a simple pattern that fulfills the requirement.
来源:https://stackoverflow.com/questions/2656548/ioc-container-handling-state-params-in-non-default-constructor