I'll invert the question and ask, why do you need so many interfaces? Whenever I see people using interfaces, the question I have is, what purpose does this serve you? Normally, people say that they use interfaces to support testing so that they can swap out implementations and mock the interface. The interesting thing about this is, a lot of the time this is theoretical because they don't have any tests. Another reason for using an interface is that it provides an abstraction for something with multiple implementations. Again, this is intereresting because it's rare for people to create code that needs multiple implementations. Now, there are places where interfaces are useful - if you're writing an API for others to use, there may be places for them, but how often is that the case?
The problem seems to arise because Microsoft documentation and guides are littered with interfaces and it has become the norm to accept that this is what people should do. Where this is especially apparent is in documentation around IoC containers. We often see something like
services.RegisterSingleton<IDog, Dog>();
. The funny thing is, it's just as easy to do
services.RegisterSingleton<Dog>();
. Then, when you need a dog instance, you are just accept
Dog
in the constructor.
I mentioned that interfaces were justified by people saying they needed to swap out implementations for testing. If you don't have an interface, how do you do this? The answer is to use (where possible), virtual methods and inheritance where needed. As an example, suppose I want to mock the behaviour of a method that accepts a (for the purposes of this example a completely made up)
RulesProviderEngine
. What might this look like? Let's look at our method first.
public async Task<ValidationRulesResults> InvokeValidationAsync(RulesProviderEngine engine, string ruleset)
{
var results = await engine.ExecuteRulesetAsync(ruleset);
Logger.LogInfo(results.ToJson());
return results;
}
To mock this, we could simply do this (assuming that
ExecuteRulesetAsync
is
virtual
).
public class MockRulesProviderEngine
{
public List<ValidationRulesResults> ExpectedResults = ValidationRulesResults.Empty;
public override Task<ValidationRulesResults> ExecuteRulesetAsync(string ruleset) => ExpectedResults;
}
Then, when you're testing, simply substitute the mock instance with the real one like so:
var results = new ValidationRulesResults();
var engine = new MockRulesProviderEngine();
var results = await myImplementation.InvokeValidationAsync(engine, " someruleset");
assert.equals(validationrulesresults.empty, results);
So, going back to my original question. Why do you think you need interfaces? Think about the code you're writing and really think hard about whether you need an interface or not.