SOLID – #4 Zasada segregacji interfejsów

SOLID jest to zestaw zasad dobrego programowania obiektowego. Stanowią fundament projektowania oprogramowania które mogą pomóc programistom tworzyć bardziej zorganizowane, elastyczne i łatwe w utrzymaniu systemy.

Czwarta zasada SOLID jest to zasada segregacji interfejsów (ang. interface segregation principle) sformułowana przez Roberta C. Martin.

Jaka jest Definicja ?

Zgodnie z zasadą segregacji interfejsów, żadna klasa nie powinna być zmuszona do implementacji interfejsów, których metody nie są związane z jej funkcjonalnością i nie są używane. W związku z tym, interfejsy powinny być tak zaprojektowane aby były konkretne, jak najmniejsze i dostosowane do funkcjonalności poszczególnych klas.

Jak to osiągnąć?

Zamiast tworzyć jeden ogólny interfejs „do wszystkich klas”, lepszym rozwiązaniem jest zdefiniowanie większej liczby małych interfejsów związanych z konkretnym zadaniem.

Poniżej zaprezentowany przykład jest błędny, ponieważ nie każda metoda definiowana przez interfejs jest wykorzystywana w klasach pochodnych. W obecnym przykładzie klasy Latte i Americano implementują interfejs IKawa, który zawiera metody których nie potrzebują do prawidłowego działania i spełnienia funkcji.

public interface IKawa {
    void dodajEspresso();
    void dodajWodę();
    void dodajSpienioneMleko();
    void dodajMlecznaPianka();
}

public class Americano implements IKawa {

    @Override
    public void dodajEspresso() {
        System.out.println("Espresso");
    }

    @Override
    public void dodajWodę() {
        System.out.println("Dodaj wodę");
    }

    @Override
    public void dodajSpienioneMleko() {
        throw new RuntimeException();
    }

    @Override
    public void dodajMlecznaPianka() {
        throw new RuntimeException();
    }
}

public class Latte implements IKawa {
    @Override
    public void dodajEspresso() {
        System.out.println("Espresso");
    }

    @Override
    public void dodajWodę() {
        throw new RuntimeException();
    }

    @Override
    public void dodajSpienioneMleko() {
        System.out.println("Spienione mleko");
    }

    @Override
    public void dodajMlecznaPianka() {
        System.out.println("Mleczna pianka");
    }
}

W kolejnym przykładzie unikniemy tego problemu, tworząc bardziej specyficzne, konkretne interfejsy – IEspresso, IWoda, ISpienioneMleko oraz IMlecznaPianka. Natomiast każda z klas będzie mogła zaimplementować tylko te interfejsy, które są zgodne z ich funkcjonalnością.

public interface IEspresso {
    void dodajEspresso();
}
public interface IWoda {
    void dodajWodę();
}
public interface ISpienioneMleko {
    void dodajSpienioneMleko();
}
public interface IMlecznaPianka {
    void dodajMlecznaPianka();
}

public class Latte implements IEspresso, ISpienioneMleko, IMlecznaPianka {
    @Override
    public void dodajEspresso() {
        System.out.println("Espresso");
    }

    @Override
    public void dodajSpienioneMleko() {
        System.out.println("Spienione mleko");
    }

    @Override
    public void dodajMlecznaPianka() {
        System.out.println("Mleczna pianka");
    }
}

public class Americano implements IEspresso, IWoda {

    @Override
    public void dodajEspresso() {
        System.out.println("Espresso");
    }

    @Override
    public void dodajWodę() {
        System.out.println("Dodaj wodę");
    }
}