Clients shouldn’t be forced to depend upon interfaces that they don’t use.
Robert C. Martin
Classes and methods we write should meet the client’s business needs.
If a class implements an interface, it should implement all of its methods. There shouldn’t be unused methods in the class. Additionally, there shouldn’t be methods that do nothing (are empty) or cause errors when used.
If a class implements an interface, it must implement all its methods. Therefore, it’s better to create more small interfaces and implement them as needed than to create overly large interfaces with too many methods that won’t be used.
Let’s consider an example interface and 3 classes implementing it:
public class Car implements Vehicle{
@Override
public void drive() {
System.out.println("Car is driving.");
}
@Override
public void floatOn() {
System.err.println("Cars don't float!");
}
@Override
public void fly() {
System.err.println("Cars don't fly!");
}
}
public class Ship implements Vehicle {
@Override
public void drive() {
System.err.println("Ships don't drive!");
}
@Override
public void floatOn() {
System.out.println("Ship is floating.");
}
@Override
public void fly() {
System.err.println("Ships don't fly!");
}
}
public class Plane implements Vehicle{
@Override
public void drive() {
System.out.println("Plane is driving.");
}
@Override
public void floatOn() {
System.out.println("Plane is floating.");
}
@Override
public void fly() {
System.out.println("Plane is flying.");
}
}
Let’s create a list of our vehicles and call each method on them:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Runner {
public static void main (String[] args) {
List<Vehicle> vehicles = new ArrayList<>(Arrays.asList(new Car(), new Ship(), new Plane()));
for (Vehicle vehicle : vehicles) {
vehicle.drive();
vehicle.floatOn();
vehicle.fly();
}
}
}
After running the example, we get:
Cars don't float!
Cars don't fly!
Ships don't drive!
Ships don't fly!
Car is driving.
Ship is floating.
Plane is driving.
Plane is floating.
Plane is flying.
All 3classes implement the interface, so they must implement all methods. But not all classes use all methods.
Improved implementation
Let’s break this interface into 3 separate interfaces.
public interface Drivable {
void drive();
}
public interface Floating {
void floatOn();
}
public interface Flying {
void fly();
}
and implement them only in the appropriate classes:
public class Car implements Drivable {
@Override
public void drive() {
System.out.println("Car is driving.");
}
}
public class Ship implements Floating {
@Override
public void floatOn() {
System.out.println("Ship is floating.");
}
}
public class Plane implements Drivable, Floating, Flying {
@Override
public void drive() {
System.out.println("Plane is driving.");
}
@Override
public void floatOn() {
System.out.println("Plane is floating.");
}
@Override
public void fly() {
System.out.println("Plane is flying.");
}
}
Let’s call the available methods on them again:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Runner {
public static void main (String[] args) {
Car car = new Car();
Ship ship = new Ship();
Plane plane = new Plane();
car.drive();
ship.floatOn();
plane.drive();
plane.floatOn();
plane.fly();
}
}
After running the program this time, we get:
Car is driving.
Ship is floating.
Plane is driving.
Plane is floating.
Plane is flying.
There are no more unused or unnecessary methods in any of the classes.
Why to use the Integration segregation principle
Code written according to the interface segregation principle is easier to develop. When creating a new class, only the necessary methods need to be implemented.
Interfaces written according to the interface segregation principle are easier to reuse. With fewer specific methods, they are more universal.