Принципы проектирования SOLID. Принцип разделения интерфейсов.

Принцип разделения интерфейсов (The Interface Segregation Principle — ISP) формулируется следующим образом: «Множество специализированных интерфейсов для клиента лучше, чем один интерфейс общего назначения». Возможно, что кое-что вам этот принцип уже напомнило, а именно принцип единственной обязанности. И ведь действительно, два этих принципа в какой-то мере между собой пересекаются. Создание какого-то одного обобщенного «суперинтерфейса» напоминает те самые god objects, которые делают много и даже больше того, чем должны. Такие сложные структуры только усложняют разработку ПО, а потому не надо собирать различные задачи в одном интерфейсе, а также преднамеренно раздувать интерфейсы, добавляя в них ненужные методы, которые могут вообще не использоваться некоторыми реализациями и содержать из-за этого бесполезные заглушки.

Рассмотрим все на примере. Допустим, что у нас есть общий интерфейс Ship, представляющий любое морское судно. Данный интерфейс реализован в нескольких классах — Brig и Halleon.

Файл Ship.java:

Файл Brig.java:

Файл Halleon.java:

И все бы было хорошо, но вот мы решили немного расширить нашу иерархию типов, добавив в систему новый класс кораблей — торговое судно. Задача торгового судна — перевозить товары, у него должен быть определены методы для получения товаров и их возврата, то есть в интерфейсе класса MerchantShip должны быть определены соответствующие методы. Также торговому судну не нужен метод attack(). Его задача успешно довести товары до точки назначения и обычно его сопровождают другие корабли, которые и должны его защищать, а также в принципе могут существовать торговые суда, не предусматривающие такого вооружения (в принципе на эту тему можно поспорить, но наша задача — показать основной принцип). Предположим, что мы решили сейчас реализовать интерфейс Ship:

Итак, в наш класс мы добавили одно новое поле — ссылку на объект класса Goods, представляющему перевозимые товары. Но что нам делать с методом attack()? Раз он не нужен нам в этом классе, то получается придется оставлять метод пустым (метод-заглушка) или выбрасывать исключение. Если такая ситуация возникает, то вероятно в проектировании была допущена какая-то ошибка. Дело в том, что наш базовый интерфейс стал тем самым интерфейсом общего назначения. Он содержит больше методов, чем это требуется. Вообще порой задача определения основного функционала для базового класса весьма непростая задача.

Принцип разделения интерфейсов как раз и говорим о том, что таких конструкций следует избегать. Поэтому лучшим решением было бы определить две базовые реализации интерфейса ShipWarriorShip (для военных кораблей) и MerchantShip (для торговых судов). А от них уже создавать еще более специализированные подклассы, например, Halleon и RomeMerchantShip(римский торговый корабль).  Первым делом исключаем метод attack() из интерфейса Ship:

Файл WarriorShip.java (который содержит дополнительный метод attack()):

Файл MerchantShip.java(не содержит лишней функциональности):

Реализацию их подклассов оставляем читателю для самостоятельной работы. Ошибочное желание наперед напичкать базовый интерфейс ненужными методами с мыслями «а вдруг пригодится…» часто приводит к созданию непонятных конструкций и множества методов-заглушек, которые не делают ровным счетом ничего. Поэтому принцип разделения интерфейсов SOLID, как и все остальные принципы проектирования стоит учитывать при разработке программ.

 

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

Ваш e-mail не будет опубликован. Обязательные поля помечены *