Klienci nie powinni być zmuszani do polegania na interfejsach, których nie używają.
Robert C. Martin
Klasy i metody które piszemy powinny odpowiadać potrzebom biznesowym klienta.
W klasie nie powinno być nieużywanych metod. Tym bardziej nie powinno być w niej metod które nic nie robią (są puste) lub ich użycie skutkuje błędem.
Jeśli klasa implementuje interfejs, musi zaimplementować wszystkie jego metody. Dlatego nie należy tworzyć zbyt dużych interfejsów ze zbyt wieloma metodami które nie zostaną użyte. Zamiast tego lepiej stworzyć więcej małych interfejsów i implementować je w razie potrzeby.
Rozważmy przykładowy interfejs i 3 implementującego go klasy:
public class Car implements Vehicle{
@Override
public void drive() {
System.out.println("Samochód jedzie.");
}
@Override
public void floatOn() {
System.err.println("Samochody nie pływają!");
}
@Override
public void fly() {
System.err.println("Samochody nie latają!");
}
}
public class Ship implements Vehicle {
@Override
public void drive() {
System.err.println("Statki nie jeżdżą!");
}
@Override
public void floatOn() {
System.out.println("Statek płynie.");
}
@Override
public void fly() {
System.err.println("Statki nie latają!");
}
}
public class Plane implements Vehicle{
@Override
public void drive() {
System.out.println("Samolot jedzie");
}
@Override
public void floatOn() {
System.out.println("Samolot płynie");
}
@Override
public void fly() {
System.out.println("Samolot leci.");
}
}
Utwórzmy jeszcze listę naszych pojazdów i wywołajmy na nich każdą z metod:
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();
}
}
}
Po uruchomieniu przykładu dostaniemy:
Samochody nie pływają!
Samochody nie latają!
Statki nie jeżdżą!
Statki nie latają!
Samochód jedzie.
Statek płynie.
Samolot jedzie
Samolot płynie
Samolot leci.
Wszystkie 3 klasy implementują interfejs więc muszą zaimplementować wszystkie metody. Ale nie wszystkie klasy używają wszystkich metod.
Poprawiona implementacja
Rozbijmy ten interfejs na 3 oddzielne interfejsy.
public interface Drivable {
void drive();
}
public interface Floating {
void floatOn();
}
public interface Flying {
void fly();
}
i zaimplementujmy je tylko we właściwych klasach:
public class Car implements Drivable {
@Override
public void drive() {
System.out.println("Samochód jedzie.");
}
}
public class Ship implements Floating {
@Override
public void floatOn() {
System.out.println("Statek płynie.");
}
}
public class Plane implements Drivable, Floating, Flying {
@Override
public void drive() {
System.out.println("Samolot jedzie");
}
@Override
public void floatOn() {
System.out.println("Samolot płynie");
}
@Override
public void fly() {
System.out.println("Samolot leci.");
}
}
Ponownie wywołajmy na nich dostępne metody:
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();
}
}
Po uruchomieniu programu tym razem dostaniemy:
Samochód jedzie.
Statek płynie.
Samolot jedzie
Samolot płynie
Samolot leci.
W żadnej z klas nie ma już nieużywanych, niepotrzebnych metod.
Zalety kodu zgodnego z zasadą segregacji interfejsów
Kod napisany zgodnie z zasadą segregacji interfejsów jest łatwiejszy w rozwijaniu. W momencie tworzenia kolejnej klasy można zaimplementować tylko potrzebne metody.
Interfejsów napisanych zgodnie z zasadą segregacji interfejsów łatwiej ponownie użyć. Mając mniej specyficznych metod są bardziej uniwersalne.