Я повсюду читал о том, как Spring побуждает вас использовать интерфейсы в вашем коде. Я этого не вижу. В вашей весенней xml-конфигурации нет понятия интерфейса. Какая часть Spring на самом деле побуждает вас использовать интерфейсы (кроме документации)?
Это очень хороший вопрос. И, как видно из ответов, пока никто не дал конкретного ответа.




Возможно, вы захотите попробовать использовать его для себя, чтобы лучше видеть это, из документации может быть не ясно, как Spring поощряет использование интерфейса.
Вот пара примеров:
Предположим, вы пишете класс, который должен читать из ресурса (например, файла), на который можно ссылаться несколькими способами (например, в пути к классам, абсолютном пути к файлу, в виде URL-адреса и т. д.). Вы хотите определить свойство org.springframework.core.io.Resource (интерфейс) в своем классе. Затем в файле конфигурации Spring вы просто выбираете фактический класс реализации (например, org.springframework.core.io.ClassPathResource, org.springframework.core.io.FileSystemResource, org.springframework.core.io.UrlResource и т. д.). Spring в основном функционирует как чрезвычайно универсальная фабрика.
Если вы хотите воспользоваться преимуществами интеграции Spring AOP (например, для добавления перехватчиков транзакций), вам в значительной степени потребуется определить интерфейсы. Вы определяете точки перехвата в своем файле конфигурации Spring, и Spring генерирует для вас прокси на основе вашего интерфейса.
Это примеры, с которыми я лично сталкивался. Я уверен, что их гораздо больше.
Принцип инверсии зависимостей хорошо это объясняет. В частности, рисунок 4.
A. High level modules should not depend on low level modules. Both should depend upon abstractions.
B. Abstraction should not depend upon details. Details should depend upon abstractions.
Перевод примеров из приведенной выше ссылки на java:
public class Copy {
private Keyboard keyboard = new Keyboard(); // concrete dependency
private Printer printer = new Printer(); // concrete dependency
public void copy() {
for (int c = keyboard.read(); c != KeyBoard.EOF) {
printer.print(c);
}
}
}
Теперь с инверсией зависимостей:
public class Copy {
private Reader reader; // any dependency satisfying the reader interface will work
private Writer writer; // any dependency satisfying the writer interface will work
public void copy() {
for (int c = reader.read(); c != Reader.EOF) {
writer.write(c);
}
}
public Copy(Reader reader, Writer writer) {
this.reader = reader;
this.writer = writer;
}
}
Теперь Copy поддерживает больше, чем просто копирование с клавиатуры на принтер.
Он может копировать с любого Reader на любой Writer, не требуя каких-либо изменений в его коде.
А теперь с Spring:
<bean id = "copy" class = "Copy">
<constructor-arg ref = "reader" />
<constructor-arg ref = "writer" />
</bean>
<bean id = "reader" class = "KeyboardReader" />
<bean id = "writer" class = "PrinterWriter" />
или возможно:
<bean id = "reader" class = "RemoteDeviceReader" />
<bean id = "writer" class = "DatabaseWriter" />
Когда вы определяете интерфейс для своих классов, это помогает с внедрением зависимостей. В ваших файлах конфигурации Spring ничего не говорится об интерфейсах - вы просто указываете имя класса.
Но если вы хотите внедрить другой класс, предлагающий «эквивалентную» функциональность, использование интерфейса действительно поможет.
Например, предположим, что у вас есть класс, который анализирует контент веб-сайта, и вы вводите его с помощью Spring. Если классы, в которые вы его вводите, знают, что это за класс на самом деле, то, чтобы изменить его, вам придется изменить много кода, чтобы использовать другой конкретный класс. Но если вы создали интерфейс Analyzer, вы могли бы так же легко внедрить свой исходный DefaultAnalyzer, как и макет DummyAnalyzer или даже другой, который делает по сути то же самое, например PageByPageAnalyzer или что-нибудь еще. Чтобы использовать один из них, вам просто нужно изменить имя класса, которое вы вводите в свои файлы конфигурации Spring, а не проходить через классы, изменяющие ваш код.
Мне потребовалось около полутора проектов, прежде чем я действительно начал видеть полезность. Как и большинство вещей (на корпоративных языках), которые в конечном итоге оказываются полезными, это сначала кажется бессмысленным добавлением работы, пока ваш проект не начнет расти, а затем вы обнаружите, сколько времени вы сэкономили, выполнив немного больше работы заранее.
Хотя то, что вы говорите, правильно, оно не ориентировано на пружину. Он ориентирован на Java.
Большинство ответов здесь представляют собой некую форму «Вы можете легко поменять реализации», но я думаю, что они не могут ответить, почему? часть. Думаю, ответ на этот вопрос - почти однозначная проверяемость. Независимо от того, используете ли вы Spring или любую другую структуру IOC, использование Dependency Injection упрощает тестирование вашего кода. В случае, если, скажем, писатель, а не PrinterWriter, вы можете имитировать интерфейс Writer в модульном тесте и убедиться, что ваш код вызывает его так, как вы ожидаете. Если вы напрямую зависите от реализации класса, единственный вариант - подойти к принтеру и проверить его, что не очень автоматизировано. Кроме того, если вы зависите от результата вызова класса, не имея возможности имитировать, это может помешать вам достичь всех путей кода в вашем тесте, тем самым снизив их качество (потенциально). Проще говоря, вы должны отделить Object создание графа из логики приложения. Это упрощает тестирование кода.
Я не видел никаких доказательств, подтверждающих утверждение о том, что модульное тестирование улучшает качество / надежность / скорость программного обеспечения.
Я бы предпочел использовать Mockito, чем писать и поддерживать кучу интерфейсов.
@mrog mockito создает фиктивную реализацию для вашего интерфейса?
@LokeshaS Mockito создает фиктивные реализации для моих классов. Обычно мне не нужно создавать отдельные интерфейсы, но Mockito тоже может издеваться над ними.
@mrog, Он действительно создает макеты для классов, но вопрос в том, зачем нам нужны интерфейсы. Ответ состоит в том, чтобы предотвратить тесную связь между слоями, тесную связь между классами (служба может использовать другую службу) и, наконец, отделить реализацию от абстракций. Обычно аргумент в том, что у нас никогда не бывает второй реализации, тогда зачем нам интерфейсы. Отвечаю: у нас есть вторая реализация в виде заглушек / моков. Помните, что написание тестируемого кода - один из аспектов разработки программного обеспечения. Сказать, что второй реализации никогда не было, - неправда.
@LokeshaS Конечно, макеты используют интерфейс, но это не явный интерфейс, а неявный интерфейс, который автоматически включается в каждый класс. Я хочу сказать, что Mockito отлично работает без необходимости писать отдельные интерфейсы для каждого класса.
@mrog, он наверняка работает отлично, но тогда цель интерфейсов - обеспечить другие аспекты, как я уже упоминал в моем предыдущем ответе. Используя cglib, вы можете избежать интерфейсов и проксировать его, но тогда цель интерфейсов иная.
@LokeshaS Я хочу сказать, что вам не нужно добавлять интерфейсы для проверки, и добавление их только по этой причине контрпродуктивно. Но если у вас есть производственный вариант использования, который выиграет от нескольких реализаций, тогда вперед.
Spring никуда не заставит вас использовать интерфейсы, это просто хорошая практика. Если у вас есть bean-компонент, который имеет некоторые свойства, которые являются интерфейсами, а не конкретными классами, вы можете просто отключить некоторые объекты с помощью макетов, реализующих тот же интерфейс, что полезно для определенных тестовых случаев.
Если вы используете, например, классы поддержки Hibernate, вы можете определить интерфейс для вашего DAO, а затем реализовать его отдельно; преимущество наличия интерфейса в том, что вы сможете настроить его с помощью перехватчиков Spring, что позволит вам упростить код; вам не нужно будет писать какой-либо код, связывающий HibernateExceptions и закрывающий сеанс в сегменте finally, и вам также не придется определять какие-либо транзакции программно, вы просто настраиваете все это декларативно с помощью Spring.
Когда вы пишете быстрые и грязные приложения, вы можете реализовать простой DAO с помощью JDBC или какой-нибудь простой фреймворк, который вы не будете использовать в окончательной версии; вы сможете легко переключить эти компоненты, если они реализуют некоторые общие интерфейсы.
легко генерировать прокси из интерфейсов.
Если вы посмотрите любое весеннее приложение, вы увидите интерфейсы обслуживания и постоянства. поэтому идиома Spring, безусловно, поощряет использование интерфейсов. нет ничего более явного, чем это.
Никто еще не упомянул, что во многих случаях нет необходимости создавать интерфейс, чтобы реализующий класс можно было быстро переключить, потому что просто не будет более одного реализующего класса.
Когда интерфейсы создаются без необходимости, классы будут создаваться парами (интерфейс плюс реализация), добавляя ненужные стандартные интерфейсы и создавая потенциальную путаницу с зависимостями, потому что в файлах конфигурации XML на компоненты иногда будут ссылаться его интерфейс, а иногда - его реализация, с никаких последствий во время выполнения, но непоследовательность в отношении соглашений о коде.
Если вы не используете интерфейсы, вы рискуете отказать в автоматическом подключении.: Иногда Spring создает прокси-класс для Bean. Этот класс Proxy не является дочерним классом реализации службы., но он повторно реализует все свои интерфейсы. Spring попытается автоматически подключить экземпляры этого Bean, однако этот класс Proxy несовместим с классом Bean. Таким образом, объявление поля с классом Bean может привести к исключениям «небезопасного назначения поля».
Вы не можете разумно знать, когда Spring собирается проксировать службу (и вы не должны), поэтому, чтобы защитить себя от этих сюрпризов, лучше всего объявить интерфейс и использовать этот интерфейс при объявлении полей с автоматическим подключением.
Написание отдельных интерфейсов добавляет сложности и шаблонного кода, который обычно не нужен. Это также усложняет отладку, потому что, когда вы щелкаете вызов метода в своей среде IDE, он показывает интерфейс, а не реализацию. Если вы не меняете реализации во время выполнения, нет необходимости идти по этому пути.
Такие инструменты, как Mockito, позволяют очень легко тестировать код с помощью внедрения зависимостей без нагромождения интерфейсов.
Помимо вопросов, связанных с DI, такие вещи, как удаленное взаимодействие Spring, полностью зависят от использования интерфейсов.