How to Avoid Dependency Injection Constructor Madness

How to avoid Dependency Injection constructor madness?

You are right that if you use the container as a Service Locator, it's more or less a glorified static factory. For lots of reasons I consider this an anti-pattern (also see this excerpt from my book).

One of the wonderful benefits of Constructor Injection is that it makes violations of the Single Responsibility Principle glaringly obvious.

When that happens, it's time to refactor to Facade Services. In short, create a new, more coarse-grained interface that hides the interaction between some or all of the fine-grained dependencies you currently require.

Dependency Injection - What to do when you have a lot of dependencies?

Can you justify (to yourself) why the class depends on 10 other classes? Are there member variables you use to tie together a subset of those classes? If so, that indicates that this class should be broken up so that the extracted class would depend on the subset and the variables that tie such state together goes in the extracted class. With 10 dependencies, it's possible that this class has simply grown too large and needs to have its internals broken up anyway.

A note regarding your final sentence: such order dependency can also be a code smell, so it's probably good not to expose it in your interface. In fact, consider whether or not the order requirements are because operations need to be carried out in a specific order (it is the complexity of the algorithm or protocol), or because you've designed your classes to be inter-dependent. If the complexity is due to your design, refactor to eliminate the ordered dependency where possible.

If you cannot refactor (the complexities are all essential and you just have a terrible coordination problem on your hands), then you can abstract the ugliness and keep users of this class shielded (builder, factory, injector, etc).

Edit: Now that I have thought about it, I am not convinced that essential complexities of your algorithm or protocol cannot be abstracted a bit (though that might be the case). Depending on your specific problem, similarities in the manipulations of those dependent classes might either be better solved with the Strategy pattern or the Observer pattern (event listeners). You might have to wrap these classes in classes that adapt them to slightly different interfaces than what they currently expose. You'd have to evaluate the tradeoff of having the code in this monster class become more readable (yay) at the expense of up to 10 more classes in your project (boo).

I'd also like to make an addendum to abstracting the construction of this class. It seems important that any class that depends on this class also use the Dependency Injection pattern. That way, if you do use a builder, factory, injector, etc. you don't accidentally rob yourself of some of the benefits of using the DI pattern (the most important in my mind is the ability to substitute mock objects for testing).

Edit 2 (based on your edit):

My first thought is "what, no logging dependency?" :)

Even knowing what the dependencies are, it's difficult to offer useful advice.

First: what are the responsibilities of everyone? Why does this class depend on controller code (the business logic) and on Model code (two different database access classes, with DAO classes)?

Depending both on DAOs and DB access classes is a code smell. What is the purpose of a DAO? What is the purpose of the DB classes? Are you trying to operate at multiple levels of abstraction?

One of the principles of OO is that data and behavior get bundled into little things called classes. Have you violated this when you created this business logic class distinct from the objects it manipulates distinct from the DAO distinct from this class? Related: Take a brief diversion into SOLID.

Second: A class to load configurations. Smells bad. Dependency Injection helps you identify dependencies and swap them out. Your monster class that depends on certain parameters. These parameters are grouped into this configuration class because...? What is the name of this configuration class? Is it DBparameters? if so, it belongs to the DB object(s), not to this class. Is it generic like Configurations? If so, you've got a mini dependency injector right there (granted, it is probably only injecting string or int values instead of composite data like classes, but why?). Awkward.

Third: The most important lesson I learned from Refactoring was that my code sucked. Not only did my code suck, but there was no single transformation to make it stop sucking. The best I could hope for was to make it suck less. Once I did that, I could make it suck less again. And again. Some design patterns are bad, but they exist to allow your sucky code to transition to less sucky code. So you take your globals and make them singletons. Then you eliminate your singletons. Don't get discouraged because you've just refactored to find that your code still sucks. It sucks less. So, your Configuration loading object may smell, but you might decide that it isn't the smelliest part of your code. In fact, you may find that the effort to "fix" it isn't worth it.

How can I use dependency injection without changing/adding constructors

What I ended up doing is this

var serviceCollection = new ServiceCollection();

serviceCollection.AddTransient<IService, Service>();

// create service provider
var serviceProvider = serviceCollection.BuildServiceProvider();

_service = ActivatorUtilities.CreateInstance<Service>(serviceProvider);

_service.DoSomething();

Thanks to this answer Dependency Injection with classes other than a Controller class

Dependency injection: Accessing services in code behind file of another project

If you want to access to the registerd services from other projects, you can use the Ioc class from the CommunityTookit.

var service = Ioc.Default.GetRequiredServices<ISecondProjectClass>();

Also, regarding to several projects use cases, you can also register your services from your second project by creating an extension method:

namespace SecondProject;
public static class SecondProjectHostBuilderExtensions
{
public static IHostBuilder AddSecondProjectServices(this IHostBuilder hostBuilder)
=> hostBuilder.ConfigureServices((context, services) =>
{
_ = services
.AddSingleton<ISecondProjectClass, SecondProjectClass>();
});
}

Call it at App.xaml.cs:

public partial class App : Application
{
private readonly IHost _host;

public App()
{
this.InitializeComponent();
_host = CreateHost();
Ioc.Default.ConfigureServices(_host.Services);
}

private static IHost CreateHost()
{
return Host
.CreateDefaultBuilder()
.AddSecondProjectServices()
.Build();
}

Then if you want to get these services at your second project:

namespace SecondProject;
public class SomeClassAtSecondProject
{
public SomeClassAtSecondProject()
{
var secondProjectClass = Ioc.Default.GetRequiredService<ISecondProjectClass>();
}
}


Related Topics



Leave a reply



Submit