Но этот код не проходит тест LSP! И не проходит по двум причинам: поведение метода add в классе DelayQueue требует, чтобы элементы реализовывали интерфейс Delayed, и даже после исправления этой проблемы реализация метода remove была, как бы это сказать, удалена. И то, и другое нарушает LSP.
Принцип разделения интерфейсов (ISP) С ООП очень легко увлечься. Например, можно создать интерфейс Document, а затем определить интерфейсы, представляющие другие документы, такие как текстовые, числовые (например, электронные таблицы) или документы презентационного типа (например, слайды). Все это хорошо, но соблазн добавить поведение в эти и без того богатые интерфейсы может привести к излишней сложности.
Например, предположим, что интерфейс Document определяет основные методы для создания, хранения и редактирования документов. Следующим шагом в развитии интерфейса может стать добавление методов форматирования документа, а затем методов печати документа (фактически говоря, документ должен "печатать сам себя"). На первый взгляд, это логично, поскольку все действия, связанные с документами, ассоциируются с интерфейсом Document.
Однако при такой реализации весь код для создания документов, хранения документов, форматирования документов, печати документов и т.д., так все области, представляющие значительную сложность, объединяются в единую реализацию, что может иметь недостатки с точки зрения поддержки, регрессионного тестирования и даже времени сборки.
ISP разделяет эти мегаинтерфейсы, признавая, что процесс создания документа сильно отличается от его форматирования и даже печати. Разработка каждой из этих областей функциональности требует своего центра экспертизы, поэтому каждая из них должна иметь свой собственный интерфейс.
Есть еще один побочный эффект — это также означает, что другим разработчикам не нужно реализовывать интерфейсы, которые они никогда не собираются поддерживать.. Например, вы можете создать какой-то слайд-одностраничник, а затем настроить его так, чтобы он передавался только по электронной почте и никогда не распечатывался. Или, в то же время, вы можете захотеть создать простой текстовый файл readme, а не документ с богатым форматированием, используемый в качестве маркетингового материала.
Хорошим примером применения ISP на практике в JDK является набор интерфейсов java.awt. Этот интерфейс очень сфокусирован и прост в использовании. Он содержит методы, которые проверяют пересечение, содержание и поддержку итерации пути по точкам фигуры. Однако фактическая работа по итерации пути для фигуры определяется интерфейсом PathIterator. Кроме того, методы для рисования объектов Shape определены в других интерфейсах и абстрактных базовых классах, таких как Graphics2D.
В этом примере java.awt не указывает Shape рисовать себя или выбирать цвет (что само по себе является богатой областью реализации); это хороший рабочий пример ISP.
Принцип инверсии зависимостей (DIP) Вполне естественно писать код более высокого уровня, который использует утилиты более низкого уровня. Возможно, у вас есть вспомогательный класс для чтения файлов или обработки XML-данных. Это хорошо, но в большинстве случаев, например, в коде, который подключается к базам данных, JMS-серверам или другим сущностям нижнего уровня, лучше не писать код непосредственно для них.
Вместо этого DIP утверждает, что конструкции как нижнего, так и верхнего уровня в коде никогда не должны напрямую зависеть друг от друга. Лучше всего поместить между ними абстракции в виде более общих интерфейсов. Например, в JDK обратите внимание на пакет java.sql.* или на API JMS и набор классов.
При использовании JDBC вы можете писать код для интерфейса Connection, не зная точно, к какой базе данных вы подключаетесь, а утилиты более низкого уровня, такие как DriverManager или DataSource, скрывают от вашего приложения детали, связанные с конкретной базой данных. В сочетании с такими фреймворками внедрения зависимостей, как Spring или Micronaut, можно даже откладывать привязку и изменять тип базы данных без изменения какого-либо кода.
В JMS API для подключения к JMS-серверу используется та же парадигма, но она идет еще дальше. Ваше приложение может использовать интерфейс Destination для отправки и получения сообщений, не зная, доставляются ли эти сообщения через
Topic или
Queue. Код нижнего уровня может быть написан или сконфигурирован для выбора парадигмы сообщений (Topic или Queue) с соответствующими деталями и характеристиками доставки сообщений, при этом не нужно менять код приложения, ведь он абстрагирован интерфейсом Destination, который находится между ними.
Вы должны писать надежный SOLID-код SOLID — это обширная и глубокая тема; здесь есть еще много, что можно узнать и куда копать. Вы можете начать с малого. Найдите утешение в том, что JDK воплощает множество принципов ООП, которые делают написание кода более эффективным и продуктивным.