ISP
#программирование

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/

Понравилось? Подписывайтесь на меня в соцсетях!

 
Twitter
VK
guest
0 Комментариев
Inline Feedbacks
View all comments
Social media & sharing icons powered by UltimatelySocial
0
Нравится? Оставьте комментарий!x
()
x