SOLID. Что значит I
I в аббревиатуре SOLID расшифровывается как Interface Segregation Principle, он же Принцип Разделения Интерфейсов. На мой взгляд, это самый простой из принципов SOLID, и сегодня мы о нем поговорим. Как обычно, разберем ISP на примере и посмотрим, что случится, если этим принципом пренебречь.
I – Interface Segregation Principle, он же ISP
Роберт Мартин в свое время провозгласил: «Программные сущности не должны зависеть от методов, которые они не используют».
Все программные сущности тех времен:
Краткое и поверхностное описание принципа разделения интерфейсов можно найти на википедии.
Суть проблемы
Проблема заключалась в том, что в былые времена еще не было большого количества хороших практик, и каждый программист (или команда программистов) писал код так, как умеет. Если результат работал – все были рады.
А затем умные дядьки вроде Мартина, Фаулера, и Макконнелла начали задумываться над тем, как сделать результат не просто рабочим, но и эффективным. И, следовательно, необходимо было научиться писать удобный и легкий в сопровождении код.
Но я немного отвлекся. Проблема «толстых» интерфейсов заключалась в том, что они содержали множество методов на все случаи жизни. А классы, которые эти интерфейсы реализовывали, нуждались лишь в некоторых методах, в то время как в остальных не было никакой нужды.
А теперь представьте ситуацию, когда класс начинает падать лишь потому, что в базовый интерфейс добавился новый метод (реализация которого не определена), либо изменилось имя одного из существующих (и не нужных классу).
И тогда Роберта Мартина осенило: «А что, если делать интерфейсы маленькими?»
ISP на примере
На мой взгляд, принцип разделения интерфейсов очень сильно коррелирует с принципом единой ответственности. Поэтому давайте возьмем пример из поста про SRP и рассмотрим его в контексте ISP.
Итак, представьте, что есть некий интерфейс, который описывает сотрудника компании:
interface Employee {}
Чем может заниматься сотрудник? Это зависит от профиля, но в общем случае сотрудник может заниматься чем угодно: вести бухгалтерию, писать программный код или мыть полы. Если мы добавим эти три метода в наш интерфейс, мы получим фуллстек-разработчика:
interface Employee {
void account(); // вести бухгалтерию
void writeCode(); // писать код
void cleanUp(); // прибраться
}
Допустим, мы хотим создать класс SoftwareEngineer
, который определит реализацию метода writeCode
:
public class SoftwareEngineer implements Employee {
@Override
public void writeCode() {
// Реализация метода
}
}
Вроде бы все нормально – наш класс разработчика реализует интерфейс Employee
и определяет реализацию метода writeCode
. Но ведь интерфейс содержит еще два метода, которым тоже необходимо задать реализацию, верно? И, так как разработчик понятия не имеет, как вести бухгалтерию, а полы мыть не может, потому что у него лапки, то класс SoftwareEngineer в конечном итоге будет выглядеть как-то так:
public class SoftwareEngineer implements Employee {
@Override
public void writeCode() {
// Реализация метода
}
@Override
public void account() {
throw new UnsupportedOperationException("Разработчик не умеет вести бухгалтерию");
}
@Override
public void cleanUp() {
throw new UnsupportedOperationException("У разработчика лапки");
}
}
Возникает вполне логичный вопрос – а можно ли как-то избавиться от двух лишних методов в классе SoftwareEngineer
и оставить только единственно необходимый?
И Роберт Мартин говорит, что да, черт возьми, можно!
ISP в деле
Мы хотим, чтобы наш класс SoftwareEngineer
определял только один метод и не содержал ничего лишнего, как в примере выше:
public class SoftwareEngineer implements Employee {
@Override
public void writeCode() {
// Реализация метода
}
}
Для этого нам нужно определить интерфейс SoftwareEngineer
с единственным методом:
interface SoftwareEngineer {
void writeCode();
}
Теперь мы можем создать три разных класса (джуниор, миддл и сениор разработчики), которые реализуют writeCode
по-разному:
public class JuniorSoftwareEngineer implements SoftwareEngineer {
@Override
public void writeCode() {
// долго думать над задачей
// задавать много вопросов
// написать корявый код с багами
}
}
public class MiddleSoftwareEngineer implements SoftwareEngineer {
@Override
public void writeCode() {
// задать пару вопросов
// понять как и зачем делать задачу
// написать рабочий код
// покрыть код тестами
}
}
public class SeniorSoftwareEngineer implements SoftwareEngineer {
@Override
public void writeCode() {
// подумать, зачем это делать
// сказать, что тут не надо ничего менять, а заказчик дурак
}
}
Аналогично с бухгалтерией и уборкой. Пусть у нас будет базовый интерфейс Cleaner
и две реализации – молодая уборщица и опытная уборщица:
interface Cleaner {
void cleanUp();
}
public class YoungCleaner implements Cleaner {
@Override
public void cleanUp() {
// строить глазки
// смахнуть пыль
}
}
public class ExperiencedCleaner implements Cleaner {
@Override
public void cleanUp() {
// ворчать
// тщательно прибраться
}
}
Что в итоге
С помощью ISP мы получим правильно и логично разделенные интерфейсы. Если мы хотим объявить, что интерфейс нужен для написания кода, то мы добавим в него только метод написания кода. Мы не станем туда добавлять ничего связанного с уборкой помещения. И наоборот – если мы создаем интерфейс для уборки помещения, мы не добавляем в него методы, связанные с кодом или бухгалтерией.
Все это поможет нашим классам быть максимально компактными и эффективно решать поставленные задачи.
Заключение
Когда вы описываете интерфейс, тщательно продумайте, какие операции он должен декларировать. Относятся ли все операции к одной области? Или же интерфейс необходимо разделить?
Конечно, вовсе необязательно делить интерфейсы настолько, чтобы они содержали по одному методу. Например, наш SoftwareEngineer
мог бы иметь еще пару методов: refactor()
, writeUnitTest()
, makeReview()
. Все эти методы описывают работу разработчика. Но если вы захотите добавить в интерфейс SoftwareEngineer
метод cleanOffice()
, то остановитесь и спросите себя, относится ли этот метод к работе разработчика? Стоит ли вынести его в отдельный интерфейс?
Не огорчайте Дядюшку Боба, создавайте проработанные и конкретные интерфейсы!
Предыдущие статьи из цикла SOLID:
S: https://baddev.ru/solid-srp/
O: https://baddev.ru/solid-ocp/
L: https://baddev.ru/solid-lsp/
Понравилось? Подписывайтесь на меня в соцсетях!
MasumINTL
[…]usually posts some quite intriguing stuff like this. If youre new to this site[…]
… [Trackback]
[…] Read More: baddev.ru/solid-isp/ […]
… [Trackback]
[…] Read More Info here on that Topic: baddev.ru/solid-isp/ […]
… [Trackback]
[…] Read More here on that Topic: baddev.ru/solid-isp/ […]