Default values ​​for constructor arguments in a library project

I am writing a library that will provide a collection of public types to its consumers.

I want to make the types from this library dependent on injection. This means that each class must have a constructor through which you can specify each individual dependency of the initialized object. I also want the library to adhere to the convention on the principle of configuration. This means that if the consumer wants the default behavior, he can use the constructor without parameters, and the object will somehow build the dependencies for himself.

In the example (C #):

public class Samurai { private readonly IWeapon _weapon; // consumers will use this constructor most of the time public Samurai() { _weapon = ??? // get an instance of the default weapon somehow } // consumers will use this constructor if they want to explicitly // configure dependencies for this instance public Samurai(IWeapon weapon) { _weapon = weapon; } } 

My first solution would be to use a service locator pattern.

The code will look like this:

 ... public Samurai() { _weapon = ServiceLocator.Instance.Get<IWeapon>(); } ... 

I have a problem with this. The service locator is marked as anti-pattern ( link ), and I completely agree with these arguments. On the other hand, Martin Fowler advocates the use of the service locator template in this situation (library projects) ( link ). I want to be careful and eliminate the possible need to rewrite the library after it discovers that the service locator was really a bad idea.

So in conclusion - do you think the service locator is good in this scenario? Should I solve my problem in a completely different way? Any thought is welcome ...

+6
source share
3 answers

If you want to make life easier for users who do not use the DI container, you can provide default instances through the highlighted Defaults class, which has these methods:

 public virtual Samurai CreateDefaultSamurai() { return new Samurai(CreateDefaultWeapon()); } public virtual IWeapon CreateDefaultWeapon() { return new Shuriken(); } 

This way, you don’t need to pollute the classes themselves with default constructors, and your users do not risk inadvertently using these default constructors.

+4
source

There is an alternative, that you enter a specific provider, say, WeaponProvider in your case in your class so that it can perform a search for you:

 public interface IWeaponProvider { IWeapon GetWeapon(); } public class Samurai { private readonly IWeapon _weapon; public Samurai(IWeaponProvider provider) { _weapon = provider.GetWeapon(); } } 

Now you can provide a local default provider for weapons:

 public class DefaultWeaponProvider : IWeaponProvider { public IWeapon GetWeapon() { return new Sword(); } } 

And since this is a local default value (unlike one from another assembly, therefore it is not a “bastard injection”), you can use it as part of your Samurai class:

 public class Samurai { private readonly IWeapon _weapon; public Samurai() : this(new DefaultWeaponProvider()) { } public Samurai(IWeaponProvider provider) { _weapon = provider.GetWeapon(); } } 
+1
source

In my C # project, I used the following approach. The goal was to achieve dependency injection (for unit / layout testing) without distorting the implementation of the code for "normal use" (i.e., having a large number of new () that are cascaded through the execution thread).

 public sealed class QueueProcessor : IQueueProcessor { private IVbfInventory vbfInventory; private IVbfRetryList vbfRetryList; public QueueProcessor(IVbfInventory vbfInventory = null, IVbfRetryList vbfRetryList = null) { this.vbfInventory = vbfInventory ?? new VbfInventory(); this.vbfRetryList = vbfRetryList ?? new VbfRetryList(); } } 

This allows the use of DI, but also means that any consumer does not have to worry about what should be the "default instance stream".

+1
source

Source: https://habr.com/ru/post/911895/


All Articles