Blog
Interface Segregation Principle
Powrót do Artykułów Dodał(a) Bartłomiej Zagórski w dniu 23.01.2022 w kategorii Zasady SOLID
Reguła Segregacji Interfejsów to czwarta zasada projektowania obiektowego zawarta w skrócie SOLID. Mówi ona, że klienci nie powinni być zmuszani do bycia zależnymi od metod, których nie używają. W praktyce należy projektować interfejsy w taki sposób, żeby były jak najmniej złożone, gdyż niesie to za sobą ryzyko, że oprogramowanie będzie mniej elastyczne. W poniższym artykule przedstawię w jakiej sytuacji występuje naruszenie zasady oraz jak ten problem rozwiązać.
Spis Treści
- Definicja
- Przykład z Życia
- Aplikacja Niezgodna z ISP
- Dodawanie Nowego Narzędzia – Benzyny
- Aplikacja Zgodna z ISP
- Korzyści płynące ze stosowania ISP
- Podsumowanie
Definicja
Żaden klient nie powinien być zmuszony do bycia zależnym od metod, z których nie korzysta.
Przykład z Życia
Dobrym przykładem jest scyzoryk. Zazwyczaj potrafi on zaspokoić około 10 potrzeb. Jest to jednak przykład przerośniętego interfejsu. Jeżeli jesteśmy amatorami wina i potrzebujemy korkociągu to wystarczy nam tylko korkociąg, a nie np. nóż czy otwieracz do puszek. W związku z tym nie należy kupować do tego celu całego scyzoryka, a wystarczy pojedynczy korkociąg.
Aplikacja Niezgodna z ISP
Chcąc pokazać jak nie należy projektować aplikacji wykorzystam dokładnie ten sam przykład co w artykule o LSP. Tym razem spojrzymy jednak na problem z innej perspektywy. Będziemy chcieli wyspecjalizować interfejsy w taki sposób, żeby nie były przerośnięte. Lepiej kilka małych, precyzyjnych niż jeden zbyt ogólny. Zobaczmy jak wygląda kod.
package pl.zagora.model;
public interface ITool {
void start();
void repair();
}
package pl.zagora.model;
public class LawnMower implements ITool {
public void start() {
System.out.println("cut the grass");
}
public void repair() {
System.out.println("sharpen the knives, adjust knives height, fix the engine");
}
}
package pl.zagora.model;
public class Scissors implements ITool {
public void start() {
System.out.println("cut the bush");
}
public void repair() {
System.out.println("sharpen blades and screw the screws");
}
}
Interfejs ITool ma dwie metody strat() i repair(). Jest implementowany przez dwie klasy rzeczywiste Gardener i ServiceTechnician, które są zobligowane do implementacji powyższych metod. Widać to na schemacie UML.

W chwili obecnej wszystko jest jeszcze w porządku i aplikacja działa poprawnie. Jednak w momencie rozszerzania ma prawo pojawić się problem, gdyż interfejs ITool jest zbyt ogólny. Zobaczmy co się stanie przy próbie dodania nowego „narzędzia” – benzyny.
Dodawanie Nowego Narzędzia – Benzyny
Nasza aplikacja się rozwija i postanowiliśmy do naszej paczki narzędzi dołożyć coś co nie do końca spełnia nasze wymagania, ale najbardziej tutaj pasuje. Jest to benzyna. Zobaczmy jakie wywoła to konsekwencje. Na początek zerknijmy na schemat UML.

package pl.zagora.model;
public class Gasoline implements ITool{
@Override
public void start() {
System.out.println("Refill tool with gasoline");
}
@Override
public void repair() {
//empty body
}
}
Klasa Gasoline musi zaimplementować obydwie metody interfejsu ITool. Jednak korzystać będzie jedynie z metody start(). Żeby aplikacja w ogóle się skompilowała musi ona zostawić metodę repair() pustą lub zrobić implementację zupełnie niepotrzebną. Jest to złamanie zasady ISP. Zostawianie pustych metod nie jest dobrą praktyką, gdyż wprowadza w błąd innych programistów, którzy mają styczność z kodem a nie znają szczegółów implementacji. W momencie wystąpienia jakiegoś problemu będą oni zmuszeni do przewertowania całego kodu i zrozumienia co autor miał na myśli. W związku z tym należy poprawić abstrakcję, która jest niefortunnie zaprojektowana.
Aplikacja Zgodna z ISP
Aby poprawić naszą aplikację, żeby nie naruszała zasady ISP. Należy rozbić tłusty interfejs ITool na dwa szczegółowe interfejsy IStartable i IRepairable. Wówczas klasy, te klasy które mają implementować obydwie metody, będą implementować obydwa interfejsy, a klasa Gasoline będzie implementować jedynie interfejs IStartable.
package pl.zagora.model;
public interface IStartable {
void start();
}
package pl.zagora.model;
public interface IRepairable {
void repair();
}
Powyżej interfejsy powstałe z rozbicia ITool, a poniżej rzeczywiste implementacje.
package pl.zagora.model;
public class LawnMower implements IStartable, IRepairable {
public void start() {
System.out.println("cut the grass");
}
public void repair() {
System.out.println("sharpen the knives, adjust knives height, fix the engine");
}
}
package pl.zagora.model;
public class Scissors implements IStartable, IRepairable {
public void start() {
System.out.println("cut the bush");
}
public void repair() {
System.out.println("sharpen blades and screw the screws");
}
}
package pl.zagora.model;
public class Gasoline implements IStartable{
@Override
public void start() {
System.out.println("Refill tool with gasoline");
}
}
Dzięki rozbiciu dużego interfejsu na małe szczegółowe mogliśmy bez problemu dołączyć do programu benzynę. Zobaczmy jak wygląda teraz schemat UML

W tym momencie klasa Gasoline nie jest zmuszona do implementacji metody, z której nie korzysta, a więc abstrakcja jest zbudowana poprawnie.
Korzyści płynące ze stosowania ISP
Dzięki projektowaniu oprogramowania zgodnie z zasadą segregacji interfejsów uzyskujemy dużą elastyczność. Możemy łatwiej rozszerzać aplikację, bo nowe klasy rzeczywiste implementują tylko te metody, których rzeczywiście używają, a klienci są zależni jedynie od tych interfejsów, których rzeczywiście potrzebują.
Podsumowanie
Zasada Segregacji Interfejsów jest według mnie najprostszą do zrozumienia i stosowania ze wszystkich zasad SOLID. Mówi, że należy stosować szczegółowe, małe interfejsy zamiast dużych ogólnych. Dzięki temu klienci nie są zmuszeni do korzystania z metod, których nie potrzebują. Pozwala to na łatwe rozszerzanie aplikacji, gdyż mamy dużą elastyczność, a abstrakcja dobrze odzwierciedla działanie aplikacji.