SOLID - Interface Segregation Principle (ISP) - Wszystko Co Powinieneś Wiedzieć o Zasadzie Segregacji Interfejsów
Przejdźmy do przykładów.
Kod #1 niestosujący się do zasady segregacji interfejsów:
public interface IReportable
{
void GeneratePdf();
void GenerateExcel();
}
public class SalaryReport : IReportable
{
public void GeneratePdf()
{
//Code to generate pdf report
}
public void GenerateExcel()
{
//Code to generate excel report
}
}
public class Invoice : IReportable
{
public void GeneratePdf()
{
//Code to generate pdf report
}
public void GenerateExcel()
{
throw new NotImplementedException();
}
}
W powyższym przykładzie mamy interfejs IReportable, który może generować pliki pdf oraz excel. Każda klasa implementująca ten interfejs powinna zaimplementować obie metody. Klasa SalaryReport zaimplementowała ten interfejs poprawnie. Dla raportu o wynagrodzeniach w firmie jest możliwość wygenerowania pliku pdf, a także pliku excel. Niestety klasa Invoice również implementuje ten interfejs, chociaż tak naprawdę potrzebuje ona, tylko jednej z metod interfejsu IReportable - GeneratePdf. W tym przypadku dla metody GenerateExcel zostanie rzucony wyjątek NotImplementedException, jest to ewidentne naruszenie zasady segregacji interfejsów. Jeżeli w kodzie jakiejś klasy są metody, które rzucają wyjątek NotImplementedException, lub po prostu są puste, to sygnał, że prawdopodobnie nasz interfejs jest za "tłusty".
Kod #1 stosujący zasadę segregacji interfejsów:
public interface IReportablePdf
{
void GeneratePdf();
}
public interface IReportableExcel
{
void GenerateExcel();
}
public class SalaryReport : IReportablePdf, IReportableExcel
{
public void GeneratePdf()
{
//Code to generate pdf report
}
public void GenerateExcel()
{
//Code to generate excel report
}
}
public class Invoice : IReportablePdf
{
public void GeneratePdf()
{
//Code to generate pdf report
}
}
Aby stosować się do reguły ISP, nasz kod może wyglądać na przykład tak jak powyższa implementacja. Zbyt ogólny interfejs IReportable został podzielony, na dwa mniejsze bardziej dedykowane interfejsy: IReportablePdf oraz IReportableExcel. Nasze klasy implementują tylko takie interfejsy, które w rzeczywistości potrzebują, o to właśnie chodzi w zasadzie segregacji interfejsów.
Kod #2 niestosujący się do zasady segregacji interfejsów:
public interface IEmployee
{
void Work();
void Eat();
}
public class Employee : IEmployee
{
public void Work()
{
//Code to work
}
public void Eat()
{
//Code to eat
}
}
public class Robot : IEmployee
{
public void Work()
{
//Code to work
}
public void Eat()
{
throw new NotImplementedException();
}
}
public class Program
{
static void Main()
{
var employess = new List<IEmployee>
{
new Employee(),
new Robot()
};
foreach (var employee in employess)
employee.Eat();//Unhandled exception
}
}
W powyższym przykładzie mamy interfejs IEmployee, który został zaimplementowany przez klasy Employee oraz Robot. Podobnie jak w przykładzie #1, tak samo tutaj łamiemy zasadę segregacji interfejsów. Klasa Robot nie potrzebuje metody Eat, mimo to musi w tym przypadku taką metodę zaimplementować. Łamiąc zasadę segregacji interfejsów, często łamiemy również kilka innych reguł SOLID. Podobnie jak w regule Open-Closed Principle, niestosowanie się również do reguły ISP, może mieć swoje konsekwencje, ponieważ iterując po liście obiektów ze wspólnym interfejsem, może zostać rzucony wyjątek, przez co musielibyśmy sprawdzać typ obiektu w klasie. Również narusza to regułę, o której pisałem w poprzednim artykule, a mianowicie Liskov Substitution Principle, ponieważ w miejsce bazowego obiektu, nie można podstawić dowolnego obiektu klasy pochodnej, bez znajomości tego obiektu. Również, jeżeli nasze klasy mają więcej niż jedną odpowiedzialność (Single Responsibilty Principle), to również ISP jest łamane - wtedy najczęściej są implementowane zbyt ogólne interfejsy.
Kod #2 stosujący zasadę segregacji interfejsów:
Prawdopodobnie domyślasz się już, jak powinna wyglądać prawidłowa implementacja powyższego kodu. Powinniśmy podzielić zbyt ogólny interfejs, na kilka (w tym przypadku dwa) bardziej szczegółowych interfejsów. Ten kod może wyglądać na przykład w taki sposób:
public interface IWork
{
void Work();
}
public interface IEat
{
void Eat();
}
public class Employee : IWork, IEat
{
public void Work()
{
//Code to work
}
public void Eat()
{
//Code to eat
}
}
public class Robot : IWork
{
public void Work()
{
//Code to work
}
}
PODSUMOWANIE
W dzisiejszym artykule starałem Ci się wyjaśnić, na czym polega czwarta z zasad SOLID, czyli I jak Interface Segregation Principle (ISP). Stosowanie zasady ISP daje nam tak zwane "high cohesion", czyli wysoką spójność. Zmiany w klasach są łatwiejsze do wprowadzania, konsumenci naszych klas mają pewność, że nie zostaną zaskoczeni wyjątkiem typu NotImplementedException. Jak widzisz, zasada segregacji interfejsów nie jest zbyt skomplikowana. Myślę, że na dwóch powyższych przykładach dobrze ją zrozumiałeś :)
Poprzedni artykuł - SOLID - Liskov Substitution Principle (LSP) - Wszystko Co Powinieneś Wiedzieć o Zasadzie Podstawienia Liskov.
Następny artykuł - SOLID - Dependency Inversion Principle (DIP) - Wszystko Co Powinieneś Wiedzieć o Zasadzie Odwrócenia Zależności.