My take on when and how to create an interface in any OOP code. In other words, SOLID reinterpreted.
SOLID lead to a lot of misconceptions in the past two decades. Some people follow it religiously, some take those as mere recommendations.
Of the five principles, only L (for Liskov substitution principle) is unquestionably still valid. All others are made obsolete by advancements in programming languages and frameworks as pointed in this brilliant article.
However, we still make the decision to write interfaces for some classes all the time. I found a very simple heuristic to guide my decision, it worked wonders for years and years. Here it is:
Extract an interface when you can name it
Perhaps it is best illustrated with an example: If I find myself writing AbstractEmailService, IEmailService or EmailServiceImpl - I am doing something wrong.
If there is no unique and meaningful names I can give to my interface and a class implementing it - there is no need for an interface to begin with.
On the other hand, if I have an interface EmailService and concrete implementations GoogleEmailService, AwsEmailService and perhaps DummyVoidEmailService - it creates a clear
and obvious separation of concerns that is going to keep my code easy to maintain in the future.
Of course it could grow complicated. I may add an abstract class RateLimitedEmailService for example. But the main rule still applies: if there is nothing I can say about a class
or interface beyond what is already clearly spelled out by my language of choice keywords accompanying its definition - this thing should should not exist.