How to deal with constructor over-injection in .NE

2019-01-14 04:03发布

问题:

I'm sorry if that question was already discussed, but I didn't find exactely what I wanted. The problem I'm facing is more about patterns and design choices than about .NET itself. I just would like to have your advice to know where to start my refactorings.

Today I opend one of the classes in my actual application and found that it has 13 dependencies injected by constructor !!! In fact each developper added the dependecy it needed in method that he was writing.

One point of my understanding of DI is that when we inject a dependency by constructor it means that it's a mandatory dependency and should be used in all methods of the class. If we need a particular dependency just in one method of the given class, what does it mean for you ?

  • The given class does too much ? I should consider to create a new type just with a needed dependency ?
  • I should inject by property ? But in that particular method a dependency is mandatory so I don't think it's a good choice.
  • I should inject by method ?

What's difficult is to find the right balance. In reallity sometimes it's not possible to encapsulate the bahaviour in the clean way.

I was considering to create something like service aggregator to hind related dependency behind one of them but would like if you have other advices. Thanks in advance.

回答1:

You are correct: if you need to inject 13 dependencies into a class, it's a pretty sure sign that you are violating the Single Responsibility Principle. Switching to Property Injection will not help, as it will not decrease the number of dependencies - it will only imply that those dependencies are optional instead of mandatory.

Basically, there are two ways to deal with this type of problem:

  • Refactor to Facade Services. This is basically a non-breaking refactoring as it maintains the functionality of the Controller. However, it changes the responsibility toward coordinating/orchestrating the interaction between services instead of managing the nitty-gritty details of the implementation.
  • Split up the class into independent classes. If each of the services were introduced by different developers to support a subset of the methods, it's a sign of low cohesion. In this case, it would be better to split up the class into several independent classes.

Specifically for ASP.NET MVC you may not want to split up an Controller because it will change your URL scheme. That's fair enough, but consider what this implies: it means that the single responsibility of the Controller should be to map URLs to application code. In other words, that's all a Controller should do, and it then follows that the correct solution is to refactor to Facade Services.



回答2:

Just because a dependency is mandatory doesn't mean it should be used in all the methods of a class, IMO. It should just be logically part of the configuration of the class itself.

On the other hand, if a class has 13 dependencies it may well be doing too much. Do any of those dependencies logically belong together? Perhaps the dependencies themselves should be "chunkier" - or quite possibly your class should do less.



回答3:

This sounds like a case of a class having too many dependencies i.e. it's a God class. Try and break it down into more discrete responsibilities.



回答4:

If your class has 13 dependencies you definitely have a problem. Clearly your class serves too many responsibilities. Marc Seemann has discussed this problem in his book "Dependency injection in .NET" paragraph 6.4. If your class starts to have 3 parameters in the constructor you must start to wonder. Is my class still serving one responsibility? If you start to have 4 or more parameters in your constructor refactor your class by start using the facade or composition pattern.



回答5:

Its just the way DI works. Its a fact. So accept it. Its fully legit in a service like a Pdf Service, Look at this code:

public PdfService(ILocalizationService localizationService, 
        ILanguageService languageService,
        IWorkContext workContext,
        IOrderService orderService,
        IPaymentService paymentService,
        IDateTimeHelper dateTimeHelper,
        IPriceFormatter priceFormatter,
        ICurrencyService currencyService, 
        IMeasureService measureService,
        IPictureService pictureService,
        IProductService productService, 
        IProductAttributeParser productAttributeParser,
        IStoreService storeService,
        IStoreContext storeContext,
        ISettingService settingContext,
        IAddressAttributeFormatter addressAttributeFormatter,
        CatalogSettings catalogSettings, 
        CurrencySettings currencySettings,
        MeasureSettings measureSettings,
        PdfSettings pdfSettings,
        TaxSettings taxSettings,
        AddressSettings addressSettings)