Страницы

Поиск по вопросам

четверг, 28 ноября 2019 г.

Когда ещё следует использовать интерфейсы

#java #любой_язык #интерфейс


Столкнулся с тем, что часто в системах объявляют интерфейсы для каждого класса из
слоёв service or repository. Соответственно каждый интерфейс имеет только одну реализацию.
Так вот, как мне кажется, это overhead. Хотелось бы узнать о том когда же все таки
следует использовать интерфейсы, а точнее стоит ли их использовать вот в таких вот
случаях. Возможно, есть какие-то хорошие практики или умные статьи по этому поводу.

Возможно, в разных языках разные практики, интересует ситуация с Java 

Проект: средних размеров enterprize монолит с двумя базами, интерфейсы общения с
базами никак не пересекаются, тесты - только юнит и спокойно (как мне кажется) можно
обходится моками реализаций
    


Ответы

Ответ 1



Не раз встречался с практикой описанной в вопросе, когда например для всех сервисов или DAO объектов или стратегий, независимо от необходимости, создавался интерфейс и его единственная имплементация. С точки зрения OOP это правильный подход (см. Dependency inversion principle), т.к. в результате модули использующие описываемый интерфейсом функционал не имеют 'design time' зависимости на какую то конкретную имплементацию. С практической точки зрения интерфейсы, также как и другие возможности языка, нужно использовать только если в этом есть реальная, а не гипотетическая необходимость. В большинстве случаев чем меньше кода, тем лучше. Тем проще разобраться в коде и навигировать по нему, что в свою очередь влияет на скорость разработки, багфиксинга, что в свою очередь влияет на стоимость расширения и поддержки. Случаи когда использование интерфейсов необходимо уже перечислены в других ответах. Первый очевиден - необходимость иметь несколько имплементаций. Второй менее очевиден до тех пор пока в нем не возникает необходимость и специфичен для Java: для поддержки множественного наследования. В Java нет нативной поддержки для этого и единственный выход это имплементация нескольких интерфейсов одним классом. Одна из псевдопричин использования интерфейсов это возможность подмены имплементаций для организации тестирования. Наверняка есть случаи когда это действительно необходимо. Для большинства же проектов это контр продуктивно, так как создает дополнительные зависимости в коде тестов на конкретные имплементации интерфейсов. Гораздо более эффективно использование зрелого Mock-фреймворка (Mockito и/или PowerMock для Java). Этот подход требует значительно меньше кода и является более гибким и наглядным, т.к. описание поведения реализуемого 'затычкой' :) находится прямо в тесте, а не где то еще, в третьем классе также используемом в 42 других тестах. Использование интерфейсов добавляет хоть и минимальную, но сложность. В тоже время операция extract interface в современных IDE является стандартной, полностью автоматизированной и занимает секунды, что делает определение интерфейсов "на всякий случай для будущих поколений" бессмысленным.

Ответ 2



Предположу, что взаимодействие слоёв делают через интерфейсы для удобства тестирования. Для того, чтобы объект или группу объектов было легко подменить другими объектами, интерфейс — наилучший, самый простой и прямой метод: вы даёте возможность тестирующему коду просто создать другую реализацию этого же интерфейсов. Таким образом, подмена выше- и нижележащих слоёв становится намного легче.

Ответ 3



Интерфейсы позволяют реализовать Dependancy Injection. Предположим у нас есть какой-то сервис, который каким-то образом выполняет определённую работу, при этом этот сервис регистрируется в системе через DI. Если мы хотим подключить другую версию сервиса, например у нас может быть одна версия при одних настройках а другая при других. Теперь мы легко можем подключать нужную версию без переделывания большого количества кода, например указывая в настройках. Приведу пример: У меня на сайте есть набор функций которые генерируют SEO концовку для URL. У меня есть три вида этой функции русская концовка английская концовка пустая концовка Мы можем написать интерфейс ILastPartURLComposerService, и при конкретных настройках сайта использовать тот или иной композер. Такая реализация легко расширяема, не нужно переписывать и дописывать уже существующий код, если необходимо добавить новый вид композера. То есть через интерфейсы мы можем подменять конкретные реализации, и у нас код уже становится отвязанным от конкретной реализации, которую мы можем лёгким способом поменять. ОБНОВЛЕНО В вашем вопросе уже содержится ответ. Соответственно каждый интерфейс имеет только одну реализацию Я же писал о ситуациях когда не подразумевается подмена реализаций. Когда для одного интерфейса существует только одна реализация, и вариативность реализаций в обозримом будущем не предвидится. Интерфейсы нужны для подмены по определению, что по одному интерфейсу работать с разными классами. Если подмена не планируется, если больше одной реализации не будет, тогда интерфейсы можно не использовать. Это уже содержится в вопросе. Просто с другой стороны может оказаться что где-то всё-таки такая подмена может пригодиться, и сразу эта ситуация не была видна.

Ответ 4



Использование интерфейсов преследует 2 цели: 1) Интерфейс это некий контракт или протокол того, что может делать конкретный класс. Допустим есть класс Животное - у любого животного достаточно много функций (не в смысле ООП, а в смысле первичного смысла этого термина). Например животные могут размножаться, могут есть/питаться, могут ходить/бегать и т.д. В терминах ООП каждая такая функция является интерфейсом. Бывают животные которые могут бегать или могут ползать. Если некий потомок класс Животное может только ползать, но не может бегать мы говорим, что класс реализует интерфейс ползающий, но не реализует интерфейс бегающий, соответственно со всеми классами реализующими интерфейс ползающий мы можем обращаться однотипно - неважно это змея или крокодил, хотя это совершенно разные классы, в то же самое время класс крокодил может не только ползать, но и бегать, так что мы можем с крокодилами обращаться однотипно как и с антилопами или зайцами: class Крокодил extends Животное implements Ползающий, Бегающий; class Антилопа extends Животное implements Бегающий; class Змея extends Животное implements Ползающий; 2) Отсюда же вытекает и вторая цель использования интерфейсов - как суррогата множественного наследования. В моем персональном представлении, все же первая функция интерфейсов важнее и первичнее.

Ответ 5



Так же использование интерфейсов необходимо для того, чтобы можно было создавать proxy через JDK's Dynamic Proxy (класс proxy объекта будет реализовывать интерфейсы вашего класса и делегировать его методы). Конечно, proxy можно создать через CGlib, не используя интерфейсы, но данный подход создает proxy при помощи наследования, что накладывает сопутствующие ограничения (например вы не сможете сделать ваш класс final) и проигрывает в производитетльности. В Spring AOP используются эти два подхода.

Ответ 6



имхо, если что то можно выкинуть и это никак не скажется на работе системы, то это однозначно оверхед. В данном конкретном случае интерфейсы действительно не нужны, можно обойтись и @LocalBean. Тесты мокаются. Прокси не нужны, т.к. есть интерсепторы. Интерфейсы возможно могут понадобиться, если Вы решитесь разбить монолит на независимые модули (EAR), которые будут общаться друг с другом, т.е. когда сервисы станут не локальными. Если же Вы пойдете дальше и разобьете монолит на микросервисы, то и интерфейс будет скорее всего уже платформонезависимым, т.е. легковесный веб сервис, например jsonrpc. Кстати, никто не запрещает использовать jsonrpc и без микросервисов, между EARами вместо классического ejb remoting. Да, чуть не забыл: в данном конкретном случае скорее всего это не актуально, но теоретически интерфейсы могут понадобиться, если компоненты пишутся разными командами и нужен т.н. интерфейс-контракт, т.е. когда интерфейсы складываются и версионизируются в отдельный артефакт, а интеграция делается параллельно и независимо от их реализации.

Комментариев нет:

Отправить комментарий