My observation is that in most cases, classes that have a constructor that takes both dependencies and primitives, break the Single Responsibility Principle or at least result in a design that is less clean, which leads to container configurations that are more fragile and harder to understand.
In most (if not all) cases, those primitives are configuration values, such as connection strings, debug options, and such.
There are a few ways you can change your design to solve this:
- In the case you are breaking the SRP, extract the code into its own type. Take for instance this example where the
NotifyCustomerHandler
takes an string notificationServiceUrl
, while that string
should have been encapsulated into some sort of NotificationService
.
- Another option is to extract the configuration values of a type into its own type. In the case of the
NotifyCustomerHandler
, it could take a dependency on an INotifyCustomerHandlerConfiguration
. In the past I used to have a single IMyApplicationConfiguration
interface, with all configuration values the application needed, but I came to the conclusion that this is a bad idea. This breaks the Interface Segregation Principle and your unit tests will start to suffer in terms of readability and maintainability.
- When you're not breaking the SRP and injecting a configuration object isn't practical (when you have a single primitive or getting too many configuration interfaces, or you just don't like that option), you can change these constructor arguments to public properties. This will allow you (with most containers) to register a delegate that will configure the instance after creation, in a way the compiler can verify this for you.