Kaue Silveira provided a good example, but missed to mention another important aspect.
In general, the point of interfaces is not to have fewer classes.
I suppose you are familiar with the terms coupling and cohesion. You always want to have code that is loosely coupled and highly cohesive.
Meaning, you do not want classes to be dependent on each other (coupled), but to share some logic in the form of inheritance, polymorphism, etc. These concepts are some of the basic pillars of quality ObjectOriented design. It is definitely worth reading about it if you are not familiar with these topics.
To go back at the question, sure you are going to usually have some duplicate if you are dealing with a complex logic that has a lot special cases. But, such issues are more related to other design patterns and principles that are solely designed to adhere to the DRY principle and resolve situations in way that generalize the approach to the solution.
The main idea behind interfaces is to set a logical structure to the classes that contributes to the uniformity of object manipulation.
Imagine a stock exchange system.
This interface has a single method called execute that will perform some application logic.
public interface Order{
void execute();
}
Other classes that might implement this interface could be Buy and Sell. It would look something like this:
public class Buy implement Order{
@Override
public void execute(){
//TODO: Some logic
}
}
Now both Buy and Sell would have similar code, maybe even some duplicate, but it is more important that when they implement the same interface you can handle them in an uniform way. You can have some StockManager class that would put both Buy orders and Sell orders in some Queue<Order>
. From this you can conclude that when working with such Queue, you would have the ability to call the execute()
method on any implementation of an Order interface.
Building on top of the previous argument, by using interfaces, and some frameworks like Spring, you get the possibility to do auto-wiring. This significantly reduces the dependence on lower level implementation classes, giving you the possibility to change low-level classes without impacting the top-level handlers. This type of application design is a common practice in service-oriented architecture (SOA).