Разве весь смысл интерфейса в том, что несколько классов придерживаются набора правил и реализаций?
Разве весь смысл интерфейса в том, что несколько классов придерживаются набора правил и реализаций?
Ответы:
Строго говоря, нет, нет, ЯГНИ применяется. Тем не менее, время, которое вы потратите на создание интерфейса, минимально, особенно если у вас есть удобный инструмент для генерации кода, выполняющий большую часть работы за вас. Если вы не уверены, понадобится ли вам интерфейс или нет, я бы сказал, что лучше ошибиться в сторону поддержки определения интерфейса.
Кроме того, использование интерфейса даже для одного класса предоставит вам другую ложную реализацию для модульных тестов, которая еще не запущена в производство. Ответ Авнера Шахар-Каштана расширяется по этому вопросу.
Я бы ответил, что нужен интерфейс или нет, не зависит от того, сколько классов его реализует. Интерфейсы - это инструмент для определения договоров между несколькими подсистемами вашего приложения; так что действительно важно то, как ваше приложение делится на подсистемы. В качестве внешнего интерфейса для инкапсулированных подсистем должны быть интерфейсы, независимо от того, сколько классов их реализует.
Вот одно очень полезное правило:
Foo
обращаться непосредственно к классу BarImpl
, вы решительно обязываетесь менять Foo
каждый раз, когда вы меняете BarImpl
. Вы в основном рассматриваете их как одну единицу кода, которая разбита на два класса.Foo
обращаетесь к интерфейсу Bar
, вы берете на себя обязательство, чтобы избежать изменений Foo
при изменении BarImpl
.Если вы определяете интерфейсы в ключевых точках вашего приложения, вы тщательно продумываете методы, которые они должны поддерживать, а какие - нет, и вы четко комментируете интерфейсы, чтобы описать, как реализация должна вести себя (и как нет), Ваше приложение будет намного легче понять, потому что эти прокомментированные интерфейсы предоставят своего рода спецификацию приложения - описание того, как оно должно вести себя. Это значительно облегчает чтение кода (вместо того, чтобы спрашивать «что, черт возьми, этот код должен делать», вы можете спросить «как этот код делает то, что должен делать»).
В дополнение ко всему этому (или фактически из-за этого) интерфейсы способствуют отдельной компиляции. Поскольку интерфейсы тривиальны для компиляции и имеют меньше зависимостей, чем их реализации, это означает, что если вы пишете класс Foo
для использования интерфейса Bar
, вы обычно можете перекомпилировать BarImpl
без необходимости перекомпиляции Foo
. В больших приложениях это может сэкономить много времени.
Foo
зависит от интерфейса , Bar
то BarImpl
могут быть изменены без перекомпиляции Foo
. 4. Более детальное управление доступом, чем в публичных / частных предложениях (один и тот же класс предоставляется двум клиентам через разные интерфейсы).
If you make class Foo refer directly to class BarImpl, you're strongly committing yourself to change Foo every time you change BarImpl
При изменении BarImpl, каких изменений можно избежать в Foo, используя interface Bar
? Поскольку сигнатуры и функциональные возможности метода не изменяются в BarImpl, Foo не потребует изменений даже без интерфейса (все назначение интерфейса). Я говорю о сценарии, где только один класс, т.е. BarImpl
реализует Bar. Для мультиклассового сценария я понимаю принцип инверсии зависимости и то, как его интерфейс полезен.
Интерфейсы предназначены для определения поведения, то есть набора прототипов функций / методов. Типы, реализующие интерфейс, будут реализовывать это поведение, поэтому, когда вы имеете дело с таким типом, вы знаете (частично), какое поведение он имеет.
Нет необходимости определять интерфейс, если вы знаете, что определенное им поведение будет использоваться только один раз. ПОЦЕЛУЙ (будь проще, глупый)
Хотя в теории у вас не должно быть интерфейса только для того, чтобы иметь интерфейс, ответ Янниса Ризоса намекает на дальнейшие осложнения:
Когда вы пишете модульные тесты и используете фиктивные фреймворки, такие как Moq или FakeItEasy (чтобы назвать два последних, которые я использовал), вы неявно создаете другой класс, который реализует интерфейс. Поиск в коде или статический анализ могут утверждать, что есть только одна реализация, но на самом деле есть внутренняя фиктивная реализация. Всякий раз, когда вы начинаете писать макеты, вы обнаружите, что извлечение интерфейсов имеет смысл.
Но подождите, это еще не все. Есть больше сценариев, где есть неявные реализации интерфейса. Например, при использовании коммуникационного стека WCF в .NET создается прокси для удаленной службы, которая снова реализует интерфейс.
В чистой среде кода я согласен с остальными ответами здесь. Однако обратите внимание на любые имеющиеся у вас фреймворки, шаблоны или зависимости, которые могут использовать интерфейсы.
Нет, они вам не нужны, и я считаю это антипаттерном для автоматического создания интерфейсов для каждой ссылки на класс.
Существует реальная стоимость создания Foo / FooImpl для всего. IDE может создать интерфейс / реализацию бесплатно, но когда вы перемещаетесь по коду, вы получаете дополнительную когнитивную нагрузку от F3 / F12 при переходе foo.doSomething()
к сигнатуре интерфейса, а не к фактической реализации, которую вы хотите. Плюс у вас есть беспорядок двух файлов вместо одного для всего.
Так что вы должны делать это только тогда, когда вам это действительно нужно для чего-то.
Теперь обращаемся к контраргументам:
Мне нужны интерфейсы для структур внедрения Dependency
Интерфейсы для поддержки платформ являются устаревшими. В Java интерфейсы раньше были требованием для динамических прокси, pre-CGLIB. Сегодня вам обычно это не нужно. Это считается прогрессом и благом для разработчиков, так как они вам больше не нужны в EJB3, Spring и т. Д.
Мне нужны макеты для юнит-тестирования
Если вы пишете свои собственные макеты и имеете две фактические реализации, тогда интерфейс уместен. Скорее всего, мы бы не обсуждали это в первую очередь, если бы в вашей кодовой базе были и FooImpl, и TestFoo.
Но если вы используете фальшивый фреймворк, такой как Moq, EasyMock или Mockito, вы можете имитировать классы и вам не нужны интерфейсы. Это аналогично настройке foo.method = mockImplementation
на динамическом языке, где можно назначать методы.
Нам нужны интерфейсы, чтобы следовать принципу инверсии зависимости (DIP)
DIP говорит, что вы строите, чтобы зависеть от контрактов (интерфейсов), а не от реализаций. Но класс - это уже контракт и абстракция. Вот для чего нужны публичные / частные ключевые слова. В университете каноническим примером было что-то вроде класса Matrix или Polynomial - потребители имеют общедоступный API для создания матриц, добавления их и т. Д., Но им не нужно заботиться о том, реализована ли матрица в разреженной или плотной форме. Не было IMatrix или MatrixImpl, необходимых для доказательства этой точки зрения.
Кроме того, DIP часто применяется на каждом уровне вызова класса / метода, а не только на границах основных модулей. Признаком того, что вы чрезмерно применяете DIP, является то, что ваш интерфейс и реализация изменяются с шагом блокировки, так что вам нужно коснуться двух файлов, чтобы внести изменения вместо одного. Если DIP применяется надлежащим образом, это означает, что ваш интерфейс не должен часто меняться. Кроме того, еще одним признаком является то, что ваш интерфейс имеет только одного реального потребителя (свое собственное приложение). Другая история, если вы создаете библиотеку классов для потребления во многих различных приложениях.
Это является следствием высказывания дяди Боба Мартина о насмешках - вам нужно только высмеивать основные архитектурные границы. В веб-приложении HTTP и доступ к БД являются основными границами. Все вызовы класса / метода между ними не являются. То же самое касается DIP.
Смотрите также:
new Mockup<YourClass>() {}
и весь класс, включая его конструкторы, будет смоделирован, будь то интерфейс, абстрактный класс или конкретный класс. Вы также можете «переопределить» поведение конструктора, если хотите. Я полагаю, есть эквивалентные способы в Mockito или Powermock.
Похоже, что ответы с обеих сторон забора можно суммировать следующим образом:
Хорошо спроектируйте и разместите интерфейсы там, где нужны интерфейсы.
Как я отметил в своем ответе на ответ Янни , я не думаю, что у вас когда-либо будет жесткое и быстрое правило об интерфейсах. Правило должно быть по определению гибким. Мое правило в отношении интерфейсов заключается в том, что интерфейс следует использовать везде, где вы создаете API. И API должен создаваться везде, где вы пересекаете границу из одной области ответственности в другую.
Для (ужасно надуманного) примера, скажем, вы создаете Car
класс. В вашем классе вам наверняка понадобится слой пользовательского интерфейса. В этом конкретном примере, он принимает форму IginitionSwitch
, SteeringWheel
, GearShift
, GasPedal
, и BrakePedal
. Так как эта машина содержит AutomaticTransmission
, вам не нужно ClutchPedal
. (А поскольку это ужасная машина, здесь нет кондиционера, радио или сиденья. На самом деле половицы тоже отсутствуют - вам просто нужно держаться за руль и надеяться на лучшее!)
Так какой из этих классов нуждается в интерфейсе? Ответом могут быть все из них или ни один из них - в зависимости от вашего дизайна.
Вы могли бы иметь интерфейс, который был бы похож на это:
Interface ICabin
Event IgnitionSwitchTurnedOn()
Event IgnitionSwitchTurnedOff()
Event BrakePedalPositionChanged(int percent)
Event GasPedalPositionChanged(int percent)
Event GearShiftGearChanged(int gearNum)
Event SteeringWheelTurned(float degree)
End Interface
В этот момент поведение этих классов становится частью интерфейса / API ICabin. В этом примере классы (если они есть), вероятно, простые, с несколькими свойствами и функцией или двумя. И что вы неявно заявляете в своем проекте, что эти классы существуют исключительно для поддержки любой конкретной реализации ICabin, которую вы имеете, и они не могут существовать сами по себе, или они бессмысленны вне контекста ICabin.
Это та же самая причина, по которой вы не тестируете модульные частные члены - они существуют только для поддержки общедоступного API, и, следовательно, их поведение должно быть проверено путем тестирования API.
Так что, если ваш класс существует исключительно для поддержки другого класса, и концептуально просматривать это не на самом деле имея свой собственный домен , то это нормально , чтобы пропустить интерфейс. Но если ваш класс настолько важен, что вы считаете, что он достаточно взрослый, чтобы иметь собственный домен, тогда продолжайте и предоставьте ему интерфейс.
РЕДАКТИРОВАТЬ:
Часто (в том числе в этом ответе) вы будете читать такие вещи, как «домен», «зависимость» (часто в сочетании с «инъекцией»), которые ничего не значат для вас, когда вы начинаете программировать (они точно не значит что-нибудь для меня). Для домена это означает именно то, на что это похоже:
Территория, на которой осуществляется господство или власть; владения суверена или содружества или тому подобное. Также используется в переносном смысле. [WordNet sense 2] [1913 Вебстер]
В терминах моего примера - давайте рассмотрим IgnitionSwitch
. В легковом автомобиле переключатель зажигания отвечает за:
Эти свойства составляют область того IgnitionSwitch
, о чем он знает и за что отвечает.
IgnitionSwitch
Не несет ответственности за GasPedal
. Переключатель зажигания полностью игнорирует педаль газа во всех отношениях. Они оба работают совершенно независимо друг от друга (хотя без них оба были бы бесполезны!).
Как я уже говорил, это зависит от вашего дизайна. Вы могли бы создать, IgnitionSwitch
который имел два значения: On ( True
) и Off ( False
). Или вы можете создать его для аутентификации предоставленного ключа и множества других действий. Трудно быть разработчиком, чтобы решить, где рисовать линии на песке - и, честно говоря, большую часть времени это абсолютно относительно. Эти строки в песке важны, хотя - это то, где находится ваш API, и, следовательно, где должны быть ваши интерфейсы.
Из MSDN :
Интерфейсы лучше подходят для ситуаций, когда вашим приложениям требуется множество, возможно, не связанных типов объектов, чтобы обеспечить определенную функциональность.
Интерфейсы более гибкие, чем базовые классы, потому что вы можете определить одну реализацию, которая может реализовать несколько интерфейсов.
Интерфейсы лучше в ситуациях, когда вам не нужно наследовать реализацию от базового класса.
Интерфейсы полезны в тех случаях, когда вы не можете использовать наследование классов. Например, структуры не могут наследовать от классов, но они могут реализовывать интерфейсы.
Как правило, в случае отдельного класса нет необходимости реализовывать интерфейс, но, учитывая будущее вашего проекта, было бы полезно формально определить необходимое поведение классов.
Чтобы ответить на вопрос: в этом есть нечто большее.
Одним из важных аспектов интерфейса является намерение.
Интерфейс - это «абстрактный тип, который не содержит данных, но демонстрирует поведение» - Интерфейс (вычисления). Так что, если это поведение или набор поведений, которые поддерживает класс, то интерфейс, вероятно, является правильным шаблоном. Если, однако, поведение (я) является неотъемлемой частью концепции, воплощенной в классе, то, скорее всего, вам вообще не нужен интерфейс.
Первый вопрос, который нужно задать, это какова природа вещи или процесса, который вы пытаетесь представить. Затем следуйте практическим причинам для реализации этой природы определенным образом.
Поскольку вы задали этот вопрос, я полагаю, что вы уже видели интересы того, чтобы интерфейс скрывал несколько реализаций. Это может проявляться по принципу инверсии зависимости.
Однако необходимость иметь интерфейс или нет, не зависит от количества его реализаций. Реальная роль интерфейса заключается в том, что он определяет контракт, в котором указывается, какая услуга должна предоставляться, а не как она должна быть реализована.
Как только контракт определен, две или более команды могут работать независимо. Скажем, вы работаете над модулем A, и это зависит от модуля B, сам факт создания интерфейса на B позволяет продолжить вашу работу, не беспокоясь о реализации B, потому что все детали скрыты интерфейсом. Таким образом, распределенное программирование становится возможным.
Хотя модуль B имеет только одну реализацию своего интерфейса, этот интерфейс все еще необходим.
В заключение, интерфейс скрывает детали реализации от своих пользователей. Программирование для интерфейса помогает писать больше документов, потому что контракт должен быть определен, писать более модульное программное обеспечение, продвигать модульные тесты и ускорять разработку.
Все ответы здесь очень хорошие. Действительно, в большинстве случаев вам не нужно реализовывать другой интерфейс. Но в некоторых случаях вы можете захотеть это сделать. Вот несколько случаев, когда я делаю это:
Этот класс реализует другой интерфейс, который я не хочу
часто показывать с классом адаптера, который соединяет сторонний код.
interface NameChangeListener { // Implemented by a lot of people
void nameChanged(String name);
}
interface NameChangeCount { // Only implemented by my class
int getCount();
}
class NameChangeCounter implements NameChangeListener, NameChangeCount {
...
}
class SomeUserInterface {
private NameChangeCount currentCount; // Will never know that you can change the counter
}
В классе используется специальная технология, которая не должна просачиваться в
основном при взаимодействии с внешними библиотеками. Даже если есть только одна реализация, я использую интерфейс, чтобы не допустить ненужного соединения с внешней библиотекой.
interface SomeRepository { // Guarantee that the external library details won't leak trough
...
}
class OracleSomeRepository implements SomeRepository {
... // Oracle prefix allow us to quickly know what is going on in this class
}
Межуровневое взаимодействие
Даже если только один класс пользовательского интерфейса когда-либо реализует один из классов домена, это обеспечивает лучшее разделение между этими уровнями и, что наиболее важно, позволяет избежать циклической зависимости.
package project.domain;
interface UserRequestSource {
public UserRequest getLastRequest();
}
class UserBehaviorAnalyser {
private UserRequestSource requestSource;
}
package project.ui;
class OrderCompleteDialog extends SomeUIClass implements project.domain.UserRequestSource {
// UI concern, no need for my domain object to know about this method.
public void displayLabelInErrorMode();
// They most certainly need to know about *that* though
public UserRequest getLastRequest();
}
Для большинства объектов должна быть доступна только часть метода.
Чаще всего это происходит, когда у меня есть конкретный метод конфигурации для конкретного класса.
interface Sender {
void sendMessage(Message message)
}
class PacketSender implements Sender {
void sendMessage(Message message);
void setPacketSize(int sizeInByte);
}
class Throttler { // This class need to have full access to the object
private PacketSender sender;
public useLowNetworkUsageMode() {
sender.setPacketSize(LOW_PACKET_SIZE);
sender.sendMessage(new NotifyLowNetworkUsageMessage());
... // Other details
}
}
class MailOrder { // Not this one though
private Sender sender;
}
Итак, в конце концов, я использую интерфейс по той же причине, что и частное поле: у другого объекта не должно быть доступа к вещам, к которым он не имеет доступа. Если у меня есть такой случай, я представляю интерфейс, даже если его реализует только один класс.
Интерфейсы действительно важны, но постарайтесь контролировать их количество.
Пройдя путь создания интерфейсов практически для всего, легко получить код «расколотых спагетти». Я полагаюсь на большую мудрость Ayende Rahien, который написал несколько очень мудрых слов по этому вопросу:
http://ayende.com/blog/153889/limit-your-abstractions-analyzing-a-ddd-application
Это его первый пост из всей серии, так что продолжайте читать!
Одной из причин, по которой вы все еще можете захотеть ввести интерфейс в этом случае, является следование принципу инверсии зависимости . То есть модуль, который использует класс, будет зависеть от его абстракции (т.е. интерфейса), а не от конкретной реализации. Он отделяет компоненты высокого уровня от компонентов низкого уровня.
Нет реальной причины что-либо делать. Интерфейсы должны вам помочь, а не программа вывода. Таким образом, даже если интерфейс реализован миллионами классов, не существует правила, которое говорит, что вы должны его создать. Вы создаете его так, что когда вы или кто-либо еще, кто использует ваш код, хочет что-то изменить, оно просачивается во все реализации. Создание интерфейса поможет вам во всех будущих случаях, когда вы можете захотеть создать другой класс, который его реализует.
Не всегда нужно определять интерфейс для класса.
Простые объекты, такие как объекты значений, не имеют нескольких реализаций. Их тоже не надо издеваться. Реализация может быть протестирована сама по себе, и когда тестируются другие классы, которые зависят от них, может использоваться объект фактического значения.
Помните, что создание интерфейса имеет свою стоимость. Его нужно обновлять по мере реализации, ему нужен дополнительный файл, и некоторые IDE будут испытывать проблемы с масштабированием реализации, а не интерфейса.
Поэтому я бы определил интерфейсы только для классов более высокого уровня, где вы хотите абстрагироваться от реализации.
Обратите внимание, что с классом вы получаете интерфейс бесплатно. Помимо реализации, класс определяет интерфейс из набора открытых методов. Этот интерфейс реализован всеми производными классами. Строго говоря, это не интерфейс, но его можно использовать точно так же. Поэтому я не думаю, что необходимо воссоздавать интерфейс, который уже существует под именем класса.