Interface Segregation Principle. Принцип разделения интерфейсов в проектировании приложений::Журнал СА
www.samag.ru
     
Поиск   
              
 www.samag.ru    Web  0 товаров , сумма 0 руб.
E-mail
Пароль  
 Запомнить меня
Регистрация | Забыли пароль?
Журнал "Системный администратор"
Журнал «БИТ»
Наука и технологии
Подписка
Где купить
Авторам
Рекламодателям
Архив номеров
Контакты
   

  Опросы
  Статьи

Разбор полетов  

Ошибок опыт трудный

Как часто мы легко повторяем, что не надо бояться совершать ошибки, мол,

 Читать далее...

Принципы проектирования  

Dependency Inversion Principle. Принцип инверсии зависимостей в разработке

Мы подошли к последнему принципу проектирования приложений из серии SOLID – Dependency

 Читать далее...

Рынок труда  

Вакансия: Администратор 1С

Администратор 1С – это специалист, который необходим любой организации, где установлены программы

 Читать далее...

Книжная полка  

Книги для профессионалов, студентов и пользователей

Книги издательства «БХВ» вышли книги для тех, кто хочет овладеть самыми востребованными

 Читать далее...

Принципы проектирования  

Interface Segregation Principle. Принцип разделения интерфейсов в проектировании приложений

Эта статья из серии «SOLID» посвящена четвертому принципу проектирования приложений – Interface

 Читать далее...

1001 и 1 книга  
19.03.2018г.
Просмотров: 10308
Комментарии: 0
Потоковая обработка данных

 Читать далее...

19.03.2018г.
Просмотров: 8497
Комментарии: 0
Релевантный поиск с использованием Elasticsearch и Solr

 Читать далее...

19.03.2018г.
Просмотров: 8598
Комментарии: 0
Конкурентное программирование на SCALA

 Читать далее...

19.03.2018г.
Просмотров: 5479
Комментарии: 0
Машинное обучение с использованием библиотеки Н2О

 Читать далее...

12.03.2018г.
Просмотров: 6158
Комментарии: 0
Особенности киберпреступлений в России: инструменты нападения и защита информации

 Читать далее...

Друзья сайта  

 Interface Segregation Principle. Принцип разделения интерфейсов в проектировании приложений

Архив номеров / 2024 / Выпуск №4 (257) / Interface Segregation Principle. Принцип разделения интерфейсов в проектировании приложений

Рубрика: Разработка /  Принципы проектирования

 ВИЗИТКА 



Ольга Федорова,
технический лидер «Альфа банка»

 

Interface Segregation Principle
Принцип разделения интерфейсов в проектировании приложений


Эта статья из серии «SOLID» посвящена четвертому принципу проектирования приложений – Interface Segregation или «разделения интерфейсов». Как обычно, начнем с оригинальной формулировки Роберта Мартина, которая звучит следующим образом: программные сущности не должны зависеть от методов, которые они не используют.

 

Чтобы это продемонстрировать, я хочу обратиться к реализации коллекции в языке Java. Как минимум, потому что это хороший пример того, что даже на уровне языков инженерные принципы не всегда реализованы должным образом и не надо относится к системным библиотекам как к идеалу инженерной индустрии. Знатоки JVM стека могут мне возразить, что для такой реализации были свои исторические причины, я не буду с ними спорить, но давайте посмотрим, что из этого вышло.

Для тех, кто не знаком с Java, я прикладываю картинку со структурой класса Collections, в принципе, для общего понимания его достаточно.

 

Итак, вы разрабатываете библиотеку, которая отправляет сообщения через TCP- сеть (разумеется, с использованием того, что выше, это же базовые возможности языка). Она достаточно умная, чтобы отправлять их ассинхронно, согласно приоритету, и накапливать смс в буфер, если все сразу не пролезли в сеть. Соответственно, при добавлении СМС в очередь у вас достаточно много логики, вы должны это дело залогировать, найти правильное место в очереди согласно приоритетам и т.д. Все работает, все счастливы.

В один непрекрасный день вы сталкиваетесь с необходимостью дать клиентскому коду возможность пробежаться по СМС, стоящим в очереди. Конечно же, вы не можете допустить чтобы кто-то модифицировал состояние коллекции, поэтому внутри библиотеки тихонько вызывается Collection.unmodifiedSet(), а вот уже результат вы отдаете клиентскому коду.

Что делает клиентский код? Видит метод add и воодушевленно вызывает его. Не всегда обоснованно, зачастую просто потому, что «почему бы и нет?». Однако, вместо ожидаемого результата, он встретит ошибку.

Здесь мы и вспоминаем оригинальную формулировку Interface Segregation. Суть заключается в том, чтобы клиентский код зависел только от тех частей системы, которые он реально использует. Если клиентскому коду не требуется использовать какие-то функции или возможности, то лучше вообще не предоставлять ему доступ к ним. Это позволяет избежать ненужных зависимостей и сделать систему более гибкой и поддерживаемой.

Рассмотрим другой пример и тоже из Java. Дабы у вас не сложилось предвзятое отношение к этому языку, сразу оговорюсь, что такие проблемы есть и в C#, и в других языках. Итак, интерфейс Collection:

 

Какие недостатки есть у исходного интерфейса? Как минимум, его можно было бы разделить на несколько более специализированных интерфейсов. Например:

  • методы модификации коллекции: add, addAll, clear и т.д.;
  • методы итерации и доступа: iterator, isEmpty и пр.;
  • конвертеры toArray, toStream;
  • наследование java object.

Это и было сделано, в частности, в языке Kotlin, который, хоть и является в общественном сознании в некотором роде синтаксическим сахаром для Java, кратно улучшил ряд концепций исходного языка. 

Давайте рассмотрим другой пример. Предположим, у нас есть интерфейс Payment с определенным набором методов. Кто-то реализует этот интерфейс, и, на первый взгляд, вся эта схема выглядит неплохо.

public interface Payment {
         
void initiatePayments();
          Object
status();
          List<Object>
getPayments();
}

public class BankPayment implements Payment {
          @Override
          public void initiatePayments() {
                   //
реализация
          }

          @Override
          public Object status() {
                   //
реализация
          }

          @Override
          public List<Object> getPayments() {
                  
// реализация
          }
}


Предположим, по мере развития программы, в какой-то момент появляется LoanPayment со своими методами, весьма похожими на те, что уже есть в Payment, за исключением небольшого отличия. И в целом, на первый взгляд, можно согласится на то, чтобы класс имплементировал интерфейс, хотя наличие Unsupported Operation уже вызывает сомнения.

public class LoanPayment implements Payment {
          @Override
          public void initiatePayments() {
                  
throw new UnsupportedOperationException("This is not
a bank payment")
;
          }

          @Override
          public Object status() {
                   //
реализация
          }

          @Override
          public List<Object> getPayments() {
                  
// реализация

          }
}

Продукт продолжает эволюционировать и внезапно возникает необходимость расширить оригинальный интерфейс Payment, добавив к нему еще два дополнительных метода.

public interface Payment {
         
void initiatePayments();

          Object
status();

          List<Object>
getPayments();

         
void intiateLoanSettlement();

         
void initiateRePayment();
}

Теперь у нас возникает неприятная ситуация: если раньше банковский платеж не поддерживал только один из оригинальных трех методов, то теперь он не поддерживает еще и новые методы интерфейса Payment.

public class BankPayment implements Payment {
          @Override
          public void initiatePayments() {
                   //
реализация
          }

          @Override
          public Object status() {
                   //
реализация
          }

          @Override
          public List<Object> getPayments() {
                  
// реализация
          }

          //
Новые методы, добавленные к интерфейсу Payment
          @Override
          public void intiateLoanSettlement() {
                  
throw new UnsupportedOperationException("This is not
a loan payment"
);
          }

          @Override
          public void initiateRePayment() {
                  
throw new UnsupportedOperationException("This is not
a
loan payment");
          }

}

Это уже делает код очень неуклюжим.

Как мы видели ранее, во-первых, это может привести к неудачным последствиям, когда клиенты кода начинают использовать функциональность, которую они не должны использовать.

Во-вторых, тестировать такой класс становится сложнее даже на уровне юнит-тестов.

В-третьих, это очевидно не финальная версия класса, и по мере развития кодовой базы поддержка такого кода будет только усложняться. 

В данном случае, возможно решить проблему путем наследования и вынесения общих методов в более верхнеуровневый интерфейс, который будет расширен оригинальным интерфейсом.

Кроме того, это подходит под принцип open-closed, о котором мы говорили ранее. Мы не модифицируем существующий код, а расширяем его. В результате код становится более чистым и соблюдает принципы SOLID.

public interface Payment {
          Object
status();
          List<Object>
getPayments();
}

public interface Bank extends Payment {
         
void initiatePayments();
}

public interface Loan extends Payment {
         
void intiateLoanSettlement();
         
void initiateRePayment();
}

Такой подход позволяет избежать проблему неподдерживаемых методов, упрощает тестирование, защищает клиента, который использует код от нежелательных последствий и делает архитектуру более гибкой.

К слову, напомню, что наличие интерфейсов в программе само по себе это хорошая практика, потому что они уменьшают связность кода, что в свою очередь снижает риск превращения продукта в Big Ball of Mud. Да, можно сказать, что большое количество интерфейсов не есть хорошо, потому что это отнимает ресурсы памяти и все такое.

С одной стороны, это правда. Но, с другой стороны, в современном мире мы чаще страдаем не от нехватки железа, а от отсутствия общего понимания происходящего в программе спустя несколько лет активной разработки. Поэтому интерфейсы – это ключевой инструмент в поддержании чистоты и структурированности кода. А правильная организация интерфейсов (в том числе, следование принципу Interface Segregation) – это залог надежной архитектуры приложения.  

  1. Федорова О. Single Responsibility Principle. «Системный администратор». 2024. №1-2.
  2. Федорова О. Open Closed Principle. «Системный администратор». 2024. №1-2.
  3. Федорова О. Принцип подстановки Барбары Лисков, и почему наследующий класс должен дополнять, а не замещать поведение базового класса. «Системный администратор». 2024. №3.



Ключевые слова: SOLID, ISP, Interface Segregation Principle, Роберт Мартин


Подпишитесь на журнал

Комментарии отсутствуют

Добавить комментарий

Комментарии могут оставлять только зарегистрированные пользователи

               Copyright © Системный администратор

Яндекс.Метрика
Tel.: (499) 277-12-41
Fax: (499) 277-12-45
E-mail: sa@samag.ru