Как интерфейсы облегчают взаимодействие между командами и компонентами
Что проверяют
Видите ли вы в интерфейсе контракт между людьми, а не только синтаксис implements. Интервьюеру важно, понимаете ли вы, как контракт позволяет двум командам работать параллельно и не ломать друг друга. Следом часто спрашивают про отличие от абстрактного класса и про то, как менять уже опубликованный интерфейс.
Как отвечать
- контракт до реализации: команды договариваются о
PaymentGatewayс методами и исключениями, после чего одна пишет реализацию, а другая уже вызывает её через заглушку. Интерфейс здесь документ, который проверяет компилятор; - инверсия зависимостей: модуль бизнес-логики объявляет интерфейс у себя (
OrderRepository), а инфраструктура его реализует. Зависимость направлена к ядру, и замена хранилища не трогает бизнес-код; - тестирование: вместо реального клиента подставляется мок через Mockito или ручной фейк, тестам не нужны база и чужой сервис;
- сегрегация: узкие интерфейсы уменьшают поверхность, о которой нужно договариваться, и число команд, которых задевает изменение;
- эволюция: с Java 8 в интерфейс можно добавить
default-метод, не сломав существующие реализации, а с Java 17sealed interfaceзакрывает круг реализаций, когда это нужно; - модули и DI: Spring внедряет реализацию по типу интерфейса, а через JPMS или
ServiceLoaderмодуль экспортирует только API-пакет и прячет реализацию.
На уровне сервисов та же идея живёт в OpenAPI или protobuf-схемах: общий контракт, который версионируется и проверяется контрактными тестами, например Spring Cloud Contract или Pact.
Где спотыкаются
Заводят интерфейс на каждый класс с единственной реализацией «на будущее». Это лишний слой, контракт нужен там, где проходит граница между модулями или командами.
Считают, что интерфейс защищает от любых изменений. Новый абстрактный метод ломает всех, кто его реализует, поэтому опубликованный контракт меняют через default-методы или новую версию.
Путают интерфейс с абстрактным классом. У интерфейса нет состояния экземпляра и конструктора, класс может реализовать несколько интерфейсов, а наследовать только один класс.