DEV Community

Cover image for 3 reasons to start using Java interfaces
Abdulcelil Cercenazi
Abdulcelil Cercenazi

Posted on

3 reasons to start using Java interfaces

what is an interface? 🚪

Oracle says: "an interface is a group of related methods with empty bodies"

I think of interfaces as a contract that a class promises the compiler to fulfill. If the class doesn't do what's promised the compiler will complain and give an error.

why use one?

Why bind myself to this contract and allow the compiler to yell at me about an unfulfilled promise?

Why don't I just write the methods inside the class and be done with it?

After spending some time in the industry I started to form an understanding of why we use them. I will list the most important three reasons from my point of view.

1 - Document APIs 📜

Let's say your team is writing a new service to be added to the poll of services your company offers, and other teams will write some other services that are going to use your service.

How will they know what method to call? what parameters to pass? what return type to expect?

Well, you guessed it, it's interfaces, the contract that ensures that those methods will always do as they describe in the interface.

Here is an interface that documents how to communicate with a registration service

public interface IRegistrationService {
    void registerNewUser(String userName);
    boolean userIsRegistered(String userName);
    void cancelRegistration(int registrationId);
    void cancelRegistration(String userName);
}
Enter fullscreen mode Exit fullscreen mode

You would write your own implementation of this interface, and other developers will take a look at the interface and understand what to do.

public class RegistrationService implements IRegistrationService {
    public void registerNewUser(String userName) {
        // production code
    }
    public boolean userIsRegistered(String userName) {
        // production code
        return false; // the result of prod code
    }
    public void cancelRegistration(int registrationId) {
        // production code
    }
    public void cancelRegistration(String userName) {
        // production code
    }
}
Enter fullscreen mode Exit fullscreen mode

2 - Write specifications 🖋

Imagine we want to write a specification of what something should do, but not write how it should be done, and leave that detail to whoever wishes to make it happen.

Perhaps the most famous example of this is the JPA (Java Persistence API)

JPA is a set of interfaces that defines how database persistence should look in Java applications. There are a couple of implementations of these interfaces

  • Hibernate
  • Toplink
  • EclipseLink
  • Apache OpenJPA
  • and many more

Let's provide an example on "How to use a car" specification

public interface CarDrivingSpecification {  
        void startEngine();  
        void pressGas();  
        int getGasLeftInTank();  
        // can the car go for the kilometers provided, taking into consideration the gas left in the tank   
        boolean canGoForGivenKms(float kms);
    }
Enter fullscreen mode Exit fullscreen mode

Here we only specify what will the class that implements this interface do, and not the actual implementation of how it does it.

3- Polymorphism 💡

The third and most important use-case of interfaces is the fact that they allow us to leverage the powerful concept of Polymorphism.

  • Inheritance also allows Polymorphism. However, a class can only extend one superclass, while a class can implement as many interfaces as needed. That's why it's far more flexible.

This can make our code much more elegant and clean. But first, what was Polymorphism?

It allows us to say a thing like: "A student object can be treated as a Human Object" or "A Car object can be treated as a Vehicle object".

But, how can Polymorphism help us write clean code? Let's write some code that DOESN'T use Polymorphism.

Let's start by declaring the entity classes
Bicycle class 🚲

import lombok.AllArgsConstructor;  
@AllArgsConstructor  
public class Bicycle {  
     private final int Id;  

     public void goForward(){  
         System.out.println("Bike forward");  
      }  
}
Enter fullscreen mode Exit fullscreen mode

Car class 🚘

import lombok.AllArgsConstructor;  
@AllArgsConstructor  
public class Car {  
     private final int Id;  

     public void goForward(){  
          System.out.println("Car forward");  
      }  
     public void checkEngine(){  
         System.out.println("Checking Car engine");  
       }  
}
Enter fullscreen mode Exit fullscreen mode

Truck class 🚚

import lombok.AllArgsConstructor;  
@AllArgsConstructor  
public class Truck {  
     private final int Id;  

     public void goForward(){  
          System.out.println("Truck forward");  
     }  
     public void checkEngine(){  
         System.out.println("Checking Truck engine");  
       }  
 }
Enter fullscreen mode Exit fullscreen mode

Now let's write the service that will utilize those classes and run the methods of every object

   public class TrafficService {  
    private List<Bicycle> getBicyclesFromMap(){  
        return Arrays.asList(new Bicycle(1), new Bicycle(2));  
    }  
    private List<Car> getCarsFromMap(){  
        return Arrays.asList(new Car(1), new Car(2));  
    }  
    private List<Truck> getTrucksFromMap(){  
        return Arrays.asList(new Truck(1), new Truck(2));  
    }  

    public void goForward(List<Bicycle> bicycles, List<Car> cars, List<Truck> trucks){  
        bicycles.forEach(Bicycle::goForward);  
        cars.forEach(Car::goForward);  
        trucks.forEach(Truck::goForward);  
    }  

    public void checkEngine(List<Car> cars, List<Truck> trucks){  
        cars.forEach(Car::checkEngine);  
        trucks.forEach(Truck::checkEngine);  
    }  

    public void makeAllTransportGoForward(){  
        goForward(getBicyclesFromMap(), getCarsFromMap(), getTrucksFromMap());  
    }  
    public void makeAllEnginesStart(){  
        checkEngine(getCarsFromMap(), getTrucksFromMap());  
    }  
}
Enter fullscreen mode Exit fullscreen mode

First of all, we had to specify a method for each class of objects to get its instances from the map. Also, note that in the goForward method we had to pass as parameters the three types of objects (Bicycle, Car, Truck), and we had to call the goForward method for each class of objects.

Now, for three entities that isn't a big problem. However, when you have 10 or even a 100 ones, that quickly becomes a problem.

Let's apply interfaces and Polymorphism and see the results

We start by defining the interfaces,
The Transport interface

public interface Transport {  
      void goForward();  
}
Enter fullscreen mode Exit fullscreen mode

The Vehicle interface which extends the Transport interface

public interface Vehicle extends Transport{
     void checkEngine();
}
Enter fullscreen mode Exit fullscreen mode

The entities classes note that we make the classes implement the interfaces.
🚲 🚘 🚚

import lombok.AllArgsConstructor;  
@AllArgsConstructor
public class Bicycle implements Transport{
    private final int id;
    public void goForward() {
        System.out.println("Bike forward");
    }
}
// other class
import lombok.AllArgsConstructor;
@AllArgsConstructor
public class Car implements Vehicle{
    private final int id;
    public void checkEngine() {
        System.out.println("Checking Car engine");
    }
    public void goForward() {
        System.out.println("Car forward");
    }
}
// other class
import lombok.AllArgsConstructor;
@AllArgsConstructor
public class Truck implements Vehicle{
    private final int id;
    public void checkEngine() {
        System.out.println("Checking Truck engine");
    }
    public void goForward() {
        System.out.println("Truck forward");
    }
}
Enter fullscreen mode Exit fullscreen mode

The service that utilizes the classes Polymorphismly

public class TrafficService {
    List<Transport> getTransportFromMap(){
        return Arrays.asList(
                new Bicycle(1), new Bicycle(2),
                new Car(1), new Car(2),
                new Truck(1), new Truck(2));
    }

    List<Vehicle> getVehicleFromMap(){
        return Arrays.asList(
                new Car(1), new Car(2),
                new Truck(1), new Truck(2));
    }

    public void goForward(List<Transport> transports){
        transports.forEach(Transport::goForward);
    }

    public void checkEngine(List<Vehicle> vehicles){
        vehicles.forEach(Vehicle::checkEngine);
    }

    public void makeAllTransportGoForward(){
        goForward(getTransportFromMap());
    }
    public void makeAllEnginesStart(){
        checkEngine(getVehicleFromMap());
    }
}
Enter fullscreen mode Exit fullscreen mode

What did the interface and Polymorphism help us do? 💪

  • Gather those objects that are of the compatible interface (Vehicle,
    Transport) in a single List data structure.

    • The goForward method handles all objects that implement the interface Transport in a single command and triggers the specific implementation of each one.
    • The same goes for the checkEngine method, which handles all objects that implements the Vehicle interface.
    • Our code got much cleaner this way!!!

Conclusion 👇🏻

Interfaces have three main usages

  1. building and documenting APIs
  2. writing specification for other people to implement
  3. making use of Polymorphism to write readable clean code.

Source code 👨‍💻

GitHub

Top comments (0)