问题
I had the following base class with several dependencies:
public abstract class ViewModel
{
private readonly ILoggingService loggingService;
public ViewModel(
ILoggingService loggingService,
...)
{
this.loggingService = loggingService;
...
}
}
In my derived class, I don't want to have to repeat all of the parameters in this base class constructor, so I did this:
public abstract class ViewModel
{
private readonly IUnityContainer container;
private ILoggingService loggingService;
...
public ViewModel(IUnityContainer container)
{
this.container = container;
}
public ILoggingService LoggingService
{
get
{
if (this.loggingService == null)
{
this.loggingService = this.container.Resolve<IUnityContainer>();
}
return this.loggingService;
}
}
...
}
Now my derived classes only need to pass one thing to my base class constructor. I also have the nice effect of having my dependencies resolved only when they are needed.
However, I have since learned it is a bad idea to pass an IOC container around. What's the best alternative design pattern, bearing in mind that many of the services passed in have been registered with my IOC container as a singleton?
回答1:
As you state, you should avoid passing the container around. This turns it into a "bag of holding", where you can no longer see what your dependencies are, and you can't easily see what's in the bag.
Instead, if you find that your constructor takes far too many parameters, this is a smell by itself. In this case, you'll often find that your class is trying to do too many things (it's violating the single responsibility principle).
Take a look at your parameter list, and see if you can group the parameters into smaller groups. For example, if your constructor takes IEmailSender
, IEventLog
and ILoggingService
, maybe what you really need is an INotificationService
which aggregates these three dependencies.
Of course, sometimes you do have a constructor with that many dependencies. In this case, the class is probably just used to gather these things together and to wire them up. If this is the case, the class should probably avoid doing any actual work.
回答2:
Passing all dependencies in the Constructor is the cleanest way.
I do not see the problem in passing parameters in derived classes. Avoid typing is the wrong motivation and there are tools like Resharper helping you to generate these constructors.
If you have a lot of dependencies it indicates, that the class violate Single Responsibility Pattern.
In many cases its also a good think to favor Composition over Inheritance. This also helps to split classes into smaller pieces which do not violate SRP.
回答3:
Just create the derived classes via the container and inject them where you need them.
Wrong example - Foo
worries about the dependencies of Bar
as it needs to instanciate Bar
.
class Foo {
SomeDependency x;
public Bar(SomeDependency x) {
this.x = x;
}
public doSomething() {
Bar bar = new Bar(x);
bar.doSomething();
}
}
class Bar {
SomeDependency x;
public Bar(SomeDependency x) {
this.x = x;
}
public void doSomething() {
// ...
}
}
Correct example - Foo
does not care how Bar
is created. Bar
gets the dependencies directly from the container.
class Foo {
SomeDependency x;
Bar bar;
public Bar(SomeDependency x, Bar bar) {
this.x = x;
this.bar = bar;
}
public doSomething() {
bar.doSomething();
}
}
class Bar {
SomeDependency x;
public Bar(SomeDependency x) {
this.x = x;
}
public void doSomething() {
// ...
}
}
回答4:
AOP could be a solution if you want consistent behavior across a series of classes. Here's an example on logging with PostSharp : http://www.sharpcrafters.com/solutions/logging
For ad-hoc logging this might not perfectly suit your needs though.
回答5:
When you have many levels of inheritance, this can be a hassle, but it's a good thing to explicitly tell what dependencies a class have (through the constructor). An alternative is Annotating Objects for Property (Setter) Injection, but I recommend that you use this only for optional dependencies, i.e. a logger.
回答6:
Avoid passing the container around. This is service location. You must invert control so that whatever creates your ViewModel gives you the dependent logging service.
Mark Seeman does this very nicely in his book with decoration. AOP is a tidy alternative, as someone has already highlighted.
Your code should become:
public ViewModel(ILoggingService logger)
{
loggingService= logger;
}
public ILoggingService LoggingService
{
get
{
return this.loggingService;
}
}
回答7:
CommonServiceLocator can give you a way to resolve a resource via a static call.
Unity docs Using Injection Attributes shows other ways to choose from if constructor injection isn't jiving.
- Annotating Objects for Property (Setter) Injection
- Annotating Objects for Method Call Injection
I love using Property setter injection for logging when I've got a common base class:
abstract class Widget
{
[Dependency]
public ILogger { set; set; } // Set it and forget it!
}
I can't say I've ever actually used Method Call Injection.
You shouldn't feel like you're doing something wrong just because you can't design everything to have all dependencies coming in via constructor all the time... in a perfect world maybe, but in practice it's nice to have options...
回答8:
I ended up creating the interface and class below using the factory pattern to reduce the number of parameters added to the constructor:
public interface IInfrastructureFactory
{
ILoggingService LoggingService { get; }
// ... Other Common Services Omitted ...
}
public class InfrastructureFactory : IInfrastructureFactory
{
private readonly ILoggingService loggingService;
// ... Other Common Services Omitted ...
public InfrastructureFactory(
ILoggingService loggingService,
// ... Other Common Services Omitted ...
)
{
this.loggingService = loggingService;
// ... Other Common Services Omitted ...
}
public ILoggingService LoggingService
{
get { return this.loggingService; }
}
// ... Other Common Services Omitted ...
}
In my IOC container, I register the IInfrastructureFactory once. In my View Model, I only have a single dependency and creating a new view model is much quicker and simpler.
public abstract class ViewModel
{
private readonly IInfrastructureFactory infrastructureFactory;
public ViewModel(IInfrastructureFactory infrastructureFactory)
{
this.infrastructureFactory = infrastructureFactory;
}
public ILoggingService LoggingService
{
get { return this.infrastructureFactory.LoggingService; }
}
// ... Other Common Services Omitted ...
}
回答9:
You should stick with your first pattern. If you are getting tired of adding those constructor variables then you have too many. Think about breaking up your class into smaller bits. This pattern is so powerful because it is self-regulating through laziness :)
If you have a global type dependency that you might want to use everywhere (Logging is a perfect example)... just use the singleton pattern... (Note I also assume the container has also been created using the singleton pattern).
public static LoggingService
{
private static ILoggingService _current;
public static ILoggingService Current
{
get
{
if(_current == null) { _current = Container.Current.Resolve<ILoggingService>(); }
return _current;
}
}
}
Then use it like...
LoggingService.Current.Log(...);
That way you don't have to inject it into everything.
You should generally avoid this pattern unless it is used in a lot of modules...
来源:https://stackoverflow.com/questions/14581096/alternative-to-passing-ioc-container-around