0

When trying to design a factory service in a project where i am using a dependency container i sometime find myself in this situation.

I have an interface, in this example ISettingsLoader. ISettingsLoader defines an interface for any class used to represent a page which displays a series of settings.

interface ISettingsLoader {
  View LoadView();
}

Then i have several implementations: more often than not each implementation has its own dependencies. These are just examples: the dependencies of each implementation are usually very different.

class AudioSettingsLoader : ISettingsLoader
{
private IAudioSettingsService  service;
public AudioSettingsLoader(IAudioSettingsService service){
  this.service = service;
}
View LoadView(){
// loads the view
}
}
class NetworkSettingsLoader : ISettingsLoader
{
private INetworkInfoService  service;
public NetworkSettingsLoader (INetworkInfoService service){
  this.service = service;
}
View LoadView(){
// loads the view
}
}

The factory used to instantiate the various implementations is something like this:

class SettingsLoaderFactory
{
  private IContainer container;
  public SettingsLoaderFactory(IContainer container){
    this.container = container;
  }
IViewLoader GetViewLoader(string settingsType){
  switch(settingsType)
  {
    case "AudioSettings":
        return this.container.resolve(typeof(AudioSettingsLoader));
    case "NetworkSettings":
        return this.container.resolve(typeof(NetworkSettingsLoader));
   //etc...


  }
}

This type of approach works decently, but you have to inject the container, which is something to be worried about.

In this case, is it appropriate to inject a container?

1

3 Answers 3

1

What you seem to be implementing is the Service Locator pattern. While most times this is considered an anti-pattern, especially when you have the power of a dependency injection framework, it might be justified in this case.

The purpose of the container is to resolve the dependencies that each settings loader requires. I think the real concern I have is how SettingsLoaderFactory is being used. Consuming classes appear to call GetViewLoader(string). Unless this settingsType argument is somehow parameterized for these other objects (coming from the user interface, for instance), those objects are using SettingsLoaderFactory as a service locator — which would be an anti-pattern.

Consider injecting the audio or network settings directly as constructor parameters to dependent objects, and use the DI container to wire these objects together. Then you can eliminate the SettingsLoaderFactory, rendering this question moot.

1

I think you are misunderstanding the purpose of dependency injection here. Do you have subtypes of AudioSettingsLoader for example? In what situation would:

this.container.resolve(typeof(AudioSettingsLoader))

yield anything other than an instance of AudioSettingsLoader? Generally a factory needs to be tightly coupled to the types it handles. Your SettingsLoaderFactory is an example of this as it makes direct reference to types like AudioSettingsLoader. So there is no benefit to having a container provide them. You are just making things more complex.

Instead, the factory actually needs to be told about implementations of IAudioSettingsService etc. Those are its injectable dependencies. So I'd expect the factory to look something like:

class SettingsLoaderFactory
{
    private AudioSettingsLoader audioSettinsgLoader;
    private NetworkSettingsLoader networkSettingsLoader;
    ...

    public SettingsLoaderFactory(
        IAudioSettingsService audioSettingsService,
        INetworkInfoService networkInfoService,
        ...)
    {
        audioSettinsgLoader = new AudioSettinsgLoader(audioSettingsService);
        networkSettingsLoader = new NetworkSettingsLoader(networkInfoService);
        ...
    }

    IViewLoader GetViewLoader(string settingsType)
    {

        switch(settingsType)
        {
            case "AudioSettings":
                return audioSettingsLoader;
            case "NetworkSettings":
                return networkSettingsLoader;
            //etc...
   

And then there's no need to inject the container.

2
  • First of all thanks for your input; I forgot to mention that one of the problem i have is dealing with a large amount of dependencies. If i had to inject 30 different implementations, would it be better to inject them or to use the container approach? Would it be viable performance wise? With the container approach the only service initialized is the one that is actually needed.
    – Acerbic
    Oct 7, 2021 at 11:38
  • 1
    @Acerbic, Are you using C#? Your code looks like it, but I'm not sure. If so, have a look at Strong Inject. It generates the container and mapping data etc at compile time. So those thirty dependencies are resolved before your application even starts. That's going to be far faster than 30 calls to a container to have it resolve dependencies at runtime.
    – David Arno
    Oct 7, 2021 at 12:27
-3

It's absolutely fine. You can abstract it away for dependency purposes (clean architecture, etc.), so, for example, if your SettingsLoaderFactory is not in an infrastructure layer or shouldn't have a dependency with the IContainer you could create an intermediate service such as IServiceResolver and a method object Resolve with an implementation in the infrastructure layer

class ContainerServiceResolver: IServiceResolver
{
  private readonly IContainer _container;

  public ContainerServiceResolver(IContainer container)
  {
     _container = container;
  }

  public object Resolve(Type type)
  {
     return _container.Resolve(type);
  }
}

and then your factory, maybe living at application layer, doesn't directly depend on the container and delegates.

class SettingsLoaderFactory
{
  private readonly IServiceResolver _serviceResolver;
  public SettingsLoaderFactory(IServiceResolver serviceResolver)
  {
     _serviceResolver = serviceResolver;
  }
  
  IViewLoader GetViewLoader(string settingsType)
  {
    switch(settingsType)
    {
      case "AudioSettings":
        return _serviceResolver.resolve(typeof(AudioSettingsLoader));
      case "NetworkSettings":
        return _serviceResolver.resolve(typeof(NetworkSettingsLoader));
     //etc...
    }
  }
}

But a factory is something to instantiate objects in that case. How to do so, it's responsibility of the factory. So if you don't need this additional level of abstraction I think your approach is perfectly valid.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.