Нужно ли мне использовать интерфейс, когда только один класс когда-либо реализует его?

Разве не вся точка интерфейса для нескольких классов придерживаться набора правил и реализаций?

190 голосов | спросил Lamin Sanneh 7 AM000000110000005931 2012, 11:38:59

15 ответов


173

Строго говоря, нет, вы этого не делаете, YAGNI . Тем не менее, время, затрачиваемое на создание интерфейса, минимально, особенно если у вас есть удобный инструмент для генерации кода, который выполняет большую часть работы для вас. Если вы не уверены в том, нужен ли вам интерфейс или нет, я бы сказал, что лучше ошибиться в сторону поддержки определения интерфейса.

Кроме того, использование интерфейса даже для одного класса предоставит вам еще одну макетную реализацию для модульных тестов, которая не работает. Ответ Авнера Шахар-Каштана расширяется по этому вопросу.

ответил yannis 7 AM000000110000001431 2012, 11:45:14
117

Я бы ответил, что если вам нужен интерфейс или нет, не зависит от того, сколько классов его реализует. Интерфейсы - это инструмент для определения контрактов между несколькими подсистемами вашего приложения; так что действительно важно, как ваше приложение разделено на подсистемы. Интерфейсы должны быть интерфейсами для инкапсулированных подсистем, независимо от того, сколько классов реализует их.

Вот одно очень полезное эмпирическое правило:

  • Если вы создадите класс Foo, обратитесь непосредственно к классу BarImpl, вы решительно обязуетесь изменять Foo каждый раз, когда вы меняете BarImpl. Вы в основном относитесь к ним как к единице кода, разделенной на два класса.
  • Если вы сделаете Foo ссылкой на интерфейс Bar, вы обязуетесь вместо этого избегать изменений на Foo при изменении BarImpl.

Если вы определяете интерфейсы в ключевых точках вашего приложения, вы внимательно относитесь к методам, которые они должны поддерживать, а какие им не нужны, и вы четко прокомментируете интерфейсы, чтобы описать, как должна себя вести реализация (и как не), ваше приложение будет намного легче понять, потому что эти прокомментированные интерфейсы будут представлять собой своего рода спецификацию приложения - описание того, как вести себя . Это значительно упрощает чтение кода (вместо того, чтобы спрашивать, «что этот код должен делать», вы можете спросить «как этот код делает то, что он должен делать»).

В дополнение ко всему этому (или фактически из-за этого) интерфейсы способствуют отдельной компиляции. Поскольку интерфейсы тривиальны для компиляции и имеют меньше зависимостей, чем их реализации, это означает, что если вы пишете класс Foo для использования интерфейса Bar, вы можете обычно перекомпилировать BarImpl без перекомпиляции Foo. В больших приложениях это может сэкономить много времени.

ответил sacundim 7 PM000000100000003031 2012, 22:00:30
91

Интерфейсы предназначены для определения поведения, то есть набора прототипов функций /методов. Типы реализации интерфейса будут реализовывать это поведение, поэтому, когда вы имеете дело с таким типом, вы знаете (частично), какое поведение оно имеет.

Нет необходимости определять интерфейс, если вы знаете, что определяемое им поведение будет использоваться только один раз. KISS (держите его простым, глупым)

ответил superM 7 AM000000110000003331 2012, 11:47:33
61

Хотя теоретически вы не должны иметь интерфейс только для использования интерфейса, Ответ Янниса Ризоса намекает на дальнейшие осложнения:

Когда вы пишете модульные тесты и используете макетные фреймворки, такие как Moq или FakeItEasy (чтобы назвать два последних, которые я использовал), вы неявно создаете еще один класс, который реализует интерфейс. Поиск кода или статического анализа может утверждать, что существует только одна реализация, но на самом деле есть внутренняя реализация макета. Всякий раз, когда вы начинаете писать mocks, вы обнаружите, что извлечение интерфейсов имеет смысл.

Но подождите, есть еще. Существует больше сценариев, в которых есть неявные реализации интерфейса. Например, используя стек обмена данными WCF .NET, генерирует прокси-сервер для удаленной службы, что также реализует интерфейс.

В чистой среде кода я согласен с остальными ответами здесь. Однако обратите внимание на любые структуры, шаблоны или зависимости, которые у вас есть, которые могут использовать интерфейсы.

ответил Avner Shahar-Kashtan 7 PM000000120000005131 2012, 12:02:51
18

Похоже, что ответы по обеим сторонам забора можно суммировать в этом:

  

Хорошо спроектируйте и установите интерфейсы, где нужны интерфейсы.

Как я отметил в своем ответе на ответ Янни , я не думаю, что у вас может быть тяжелая и быстрое правило о интерфейсах. Правило должно быть, по определению, гибким. Мое правило на интерфейсах заключается в том, что интерфейс должен использоваться везде, где вы создаете API. И API должен быть создан везде, где вы пересекаете границу из одного домена ответственности в другой.

Для (ужасно надуманного) примера предположим, что вы строите класс Car. В вашем классе вам наверняка понадобится слой пользовательского интерфейса. В этом конкретном примере он принимает форму IginitionSwitch, SteeringWheel, GearShift, GasPedal и BrakePedal. Поскольку этот автомобиль содержит AutomaticTransmission, вам не нужен ClutchPedal. (И поскольку это ужасная машина, там нет A /C, радио или места. По сути, половицы тоже не хватает - вам просто нужно держаться за руль и надеяться на лучшее!)

Итак, какой из этих классов нужен интерфейс? Ответ может быть всем из них, или ни один из них - в зависимости от вашего дизайна.

У вас может быть интерфейс, похожий на этот:

Интерфейс ICabin
    Событие IgnitionSwitchTurnedOn ()
    Событие IgnitionSwitchTurnedOff ()
    Событие BrakePedalPositionChanged (int percent)
    Событие GasPedalPositionChanged (int percent)
    Событие GearShiftGearChanged (int gearNum)
    Event SteeringWheelTurned (степень плавания)
Конечный интерфейс

В этот момент поведение этих классов становится частью интерфейса /API ICabin. В этом примере классы (если они есть), вероятно, просты, с несколькими свойствами и функцией или двумя. И то, что вы неявно заявляете в своем дизайне, состоит в том, что эти классы существуют исключительно для поддержки конкретной конкретной реализации ICabin, которые у вас есть, и они не могут существовать сами по себе, или они не имеют смысла вне контекста ICabin.

По той же причине, что вы не проверяете отдельные члены-члены - они существуют только для поддержки общедоступного API, и поэтому их поведение должно быть проверено путем тестирования API.

Итак, если ваш класс существует исключительно для поддержки другого класса, и концептуально вы рассматриваете его как не действительно , имея свой собственный домен, тогда хорошо пропустить интерфейс. Но если ваш класс достаточно важен, вы считаете, что он достаточно взрослый, чтобы иметь собственный домен, а затем дайте ему интерфейс.


EDIT:

Часто (в том числе и в этом ответе) вы будете читать такие вещи, как «домен», «зависимость» (часто в сочетании с «инъекцией»), которые не означают для вас, когда вы начинаете программировать (они уверены ничего не значат для меня). Для домена это означает, что это звучит так:

  

территория, на которой действует власть или власть;           владения суверенного или содружества, или           как. Также используется образно. [WordNet смысл 2]           [1913 Вебстер]

В терминах моего примера - рассмотрим IgnitionSwitch. В коробке с мэтсовым ключом ключ зажигания отвечает за:

  1. Аутентификация (не идентифицирование) пользователя (ему нужен правильный ключ)
  2. Предоставление тока стартеру, чтобы он действительно мог запустить автомобиль.
  3. Предоставление тока системе зажигания, чтобы он мог продолжать работать
  4. Отключение тока, чтобы автомобиль остановился.
  5. В зависимости от того, как вы его просматриваете, в большинстве (всех?) новых автомобилей есть переключатель, который предотвращает удаление ключа из замка зажигания, в то время как передача выходит из парка, поэтому это может быть частью его домена. (На самом деле это означает, что мне нужно переосмыслить и перепроектировать мою систему ...)

Эти свойства составляют домен IgnitionSwitch, или, другими словами, что он знает и несет ответственность за.

IgnitionSwitch не отвечает за GasPedal. Переключатель зажигания полностью не знает педали газа во всех отношениях. Они оба действуют полностью независимо друг от друга (хотя автомобиль был бы бесполезен без них!).

Как я изначально заявил, это зависит от вашего дизайна. Вы можете создать IgnitionSwitch, который имеет два значения: On (True) и Off (False)). Или вы можете создать его для проверки подлинности предоставленного для него ключа и множества других действий. Это сложная часть того, чтобы быть разработчиком, решающим, где рисовать линии в песке - и, честно говоря, большую часть времени это полностью относительный. Эти линии в песке важны, хотя - вот где ваш API, и, следовательно, где ваши интерфейсы должны быть.

ответил Wayne Werner 8 PM00000030000002931 2012, 15:41:29
16

Нет, они вам не нужны, и я считаю, что анти-шаблон автоматически создает интерфейсы для каждой ссылки класса.

Существует реальная стоимость создания Foo /FooImpl для всего. IDE может бесплатно создавать интерфейс /реализацию, но когда вы выполняете навигацию, у вас есть дополнительная когнитивная нагрузка от F3 /F12 на foo.doSomething (), которая ведет вас к сигнатуре интерфейса, а не фактическая реализация, которую вы хотите. Кроме того, у вас есть беспорядок из двух файлов вместо одного для всего.

Поэтому вы должны делать это только тогда, когда вам действительно нужно что-то.

Теперь обращаясь к контраргументам:

Мне нужны интерфейсы для инфраструктур инъекций зависимостей

Интерфейсы для поддержки фреймворков являются устаревшими. В Java интерфейсы были требованием для динамических прокси, pre-CGLIB. Сегодня вам это обычно не нужно. Это считается успехом и благом для производительности разработчиков, которые вам больше не нужны в EJB3, Spring и т. Д.

Мне нужны макеты для модульного тестирования

Если вы пишете свои собственные макеты и имеете две фактические реализации, тогда соответствующий интерфейс. Вероятно, у нас не было бы такого обсуждения, если бы ваша кодовая база имела как FooImpl, так и TestFoo.

Но если вы используете насмешливую инфраструктуру, такую ​​как Moq, EasyMock или Mockito, вы можете имитировать классы, и вам не нужны интерфейсы. Это аналогично установке foo.method = mockImplementation на динамическом языке, где могут быть назначены методы.

Нам нужны интерфейсы, чтобы следовать Принципу инверсии зависимостей (DIP)

DIP говорит, что вы строите, чтобы зависеть от контрактов (интерфейсов), а не от реализации. Но класс - это уже контракт и абстракция. Вот для чего нужны общедоступные /частные ключевые слова. В университете канонический пример был чем-то вроде класса Matrix или Polynomial - потребители имеют открытый API для создания матриц, добавления их и т. Д., Но им не разрешается заботиться о том, реализована ли матрица в разреженной или плотной форме. Для подтверждения этой точки не было IMatrix или MatrixImpl.

Кроме того, DIP часто переопределяется на каждом уровне вызова класса /метода не только на основных границах модулей. Знак того, что вы используете DIP, заключается в том, что ваш интерфейс и реализация меняются в режиме блокировки, так что вам нужно коснуться двух файлов, чтобы внести изменения вместо одного. Если DIP применяется надлежащим образом, это означает, что ваш интерфейс не должен часто меняться. Кроме того, еще один признак заключается в том, что ваш интерфейс имеет только одного реального потребителя (собственное приложение). Разная история, если вы создаете библиотеку классов для потребления во многих различных приложениях.

Это следствие дяди Боба Мартина о насмешливости - вам нужно только высмеивать основные архитектурные границы. В webapp доступ к HTTP и БД являются основными границами. Все вызовы класса /метода между ними отсутствуют. То же самое касается DIP.

См. также:

ответил wrschneider 26 J000000Sunday15 2015, 15:02:23
8

Нет (YAGNI) , если вы не планируете писать тесты для других классов, используя этот интерфейс, и те тесты выиграют от насмешивания интерфейса.

ответил stijn 7 AM000000110000004931 2012, 11:45:49
8

От MSDN :

  

Интерфейсы лучше подходят для ситуаций, в которых вашим приложениям требуется множество, возможно, несвязанных типов объектов, чтобы обеспечить определенную функциональность.

     

Интерфейсы более гибкие, чем базовые классы, потому что вы можете определить одну реализацию, которая может реализовать несколько интерфейсов.

     

Интерфейсы лучше в ситуациях, когда вам не нужно наследовать реализацию из базового класса.

     

Интерфейсы полезны в случаях, когда вы не можете использовать наследование класса. Например, структуры не могут наследовать от классов, но они могут реализовывать интерфейсы.

Как правило, в случае одного класса нет необходимости реализовывать интерфейс, но, учитывая будущее вашего проекта, было бы полезно формально определить необходимое поведение классов.

ответил lwm 7 AM000000110000004631 2012, 11:56:46
5

Чтобы ответить на вопрос: есть нечто большее, чем это.

Одним из важных аспектов интерфейса является намерение.

Интерфейс является «абстрактным типом, который не содержит данных, а раскрывает поведение» - Интерфейс (вычисления) Итак, если это поведение или набор действий, которые поддерживает класс, чем интерфейс, вероятно, правильный шаблон. Если, однако, поведение (ы) является (являются) неотъемлемой частью концепции, воплощенной классом, то вы, вероятно, вообще не хотите никакого интерфейса.

Первый вопрос, который задают, - это характер вещи или процесса, которые вы пытаетесь представить. Затем следуйте практическим соображениям для реализации этого характера определенным образом.

ответил Joshua Drake 7 PM00000040000001431 2012, 16:55:14
5

Поскольку вы задали этот вопрос, я полагаю, что вы уже видели интересы интерфейса, скрывающего несколько реализаций. Это может быть выражено принципом инверсии зависимостей.

Однако необходимость иметь интерфейс или нет не зависит от количества его реализаций. Реальная роль интерфейса заключается в том, что он определяет контракт, в котором указывается, какая служба должна предоставляться вместо того, как она должна быть реализована.

После определения контракта две или более команды могут работать независимо. Скажем, вы работаете над модулем A, и это зависит от модуля B, факт создания интерфейса на B позволяет продолжить вашу работу, не беспокоясь о реализации B, потому что все детали скрыты интерфейсом. Таким образом, становится возможным распределенное программирование.

Несмотря на то, что модуль B имеет только одну реализацию своего интерфейса, интерфейс по-прежнему необходим.

В заключение, интерфейс скрывает детали реализации от своих пользователей. Программирование на интерфейс помогает писать больше документов, потому что контракт должен быть определен, писать более модульное программное обеспечение, продвигать модульные тесты и ускорять скорость разработки.

ответил Hui Wang 13 ThuEurope/Moscow2012-12-13T14:56:32+04:00Europe/Moscow12bEurope/MoscowThu, 13 Dec 2012 14:56:32 +0400 2012, 14:56:32
4

Все ответы здесь очень хорошие. В большинстве случаев вам не требуется для реализации другого интерфейса. Но есть случаи, когда вы можете хотеть сделать это в любом случае. Вот какой-то случай, когда я это делаю:

Класс реализует другой интерфейс, который я не хочу раскрывать
Часто случается с классом адаптера, который соединяет сторонний код.

interface NameChangeListener {//Реализован многими людьми
    void nameChanged (имя строки);
}

interface NameChangeCount {//Выполняется только моим классом
    int getCount ();
}

class NameChangeCounter реализует NameChangeListener, NameChangeCount {
    ...
}

class SomeUserInterface {
    private NameChangeCount currentCount; //Никогда не узнаем, что вы можете изменить счетчик
}

Класс использует определенную технологию, которая не должна протекать через
В основном при взаимодействии с внешними библиотеками. Даже если есть только одна реализация, я использую интерфейс для обеспечения того, чтобы я не вводил ненужную связь с внешней библиотекой.

interface SomeRepository {//Гарантировать, что данные внешней библиотеки не будут просачиваться в корыто
    ...
}

класс OracleSomeRepository реализует SomeRepository {
    ... //Префикс Oracle позволяет нам быстро узнать, что происходит в этом классе
}

Связь между уровнями
Даже если только один класс пользовательского интерфейса когда-либо реализует один из классов домена, он обеспечивает лучшее разделение между этими слоями и, самое главное, избегает циклической зависимости.

package project.domain;

интерфейс UserRequestSource {
    public UserRequest getLastRequest ();
}

класс UserBehaviorAnalyser {
    частный UserRequestSource requestSource;
}

пакет project.ui;

class OrderCompleteDialog расширяет SomeUIClass реализует project.domain.UserRequestSource {
    //UI беспокоиться, не нужно, чтобы мой объект домена знал об этом методе.
    public void displayLabelInErrorMode ();

    //Они, безусловно, должны знать о *, что * хотя
    public UserRequest getLastRequest ();
}

Только часть подмножества метода должна быть доступна для большинства объектов
В основном случается, когда у меня есть какой-то метод настройки для конкретного класса

interface Sender {
    void sendMessage (сообщение сообщения)
}

класс PacketSender реализует Sender {
    void sendMessage (Message message);
    void setPacketSize (int sizeInByte);
}

class Throttler {//Этот класс должен иметь полный доступ к объекту
    частный отправитель PacketSender;

    public useLowNetworkUsageMode () {
        sender.setPacketSize (LOW_PACKET_SIZE);
        sender.sendMessage (новый NotifyLowNetworkUsageMessage ());

        ... //Другие детали
    }
}

class MailOrder {//Не этот, хотя
    частный отправитель Отправителя;
}

Итак, в конце я использую интерфейс по той же причине, что и я использую личное поле: у другого объекта не должно быть доступа к материалам, к которым они не должны обращаться. Если у меня есть такой случай, я представляю интерфейс, даже если его реализует только класс one .

ответил Laurent Bourgault-Roy 13 ThuEurope/Moscow2012-12-13T23:55:50+04:00Europe/Moscow12bEurope/MoscowThu, 13 Dec 2012 23:55:50 +0400 2012, 23:55:50
2

Интерфейсы действительно важны, но попробуйте контролировать их количество.

Спустившись по пути создания интерфейсов практически для всего, легко получить код «нарезанный спагетти». Я полагаюсь на большую мудрость Айенде Рахиена, который опубликовал несколько очень мудрых слов по этому вопросу:

http://ayende.com/blog/153889 /предел-ваши-абстракции-анализирующее-а-ддд-приложение

Это его первое сообщение целой серии, поэтому продолжайте читать!

ответил Lord Spa 7 PM00000050000000731 2012, 17:30:07
2

Одна из причин, по которой вы, возможно, захотите ввести интерфейс в этом случае, - это следовать Принцип инверсии зависимостей . То есть модуль, который использует класс, будет зависеть от его абстракции (т. Е. Интерфейса), а не от конкретной реализации. Он отделяет компоненты высокого уровня от низкоуровневых компонентов.

ответил dodgy_coder 8 AM00000050000001431 2012, 05:50:14
2

Нет никакой реальной причины ничего делать. Интерфейсы должны помочь вам, а не программе вывода. Так что даже если интерфейс реализован миллионными классами, нет правила, согласно которому вы должны его создать. Вы создаете его так, чтобы, когда вы или кто-либо другой, кто использует ваш код, хочет изменить что-то, что оно передает для всех реализаций. Создание интерфейса поможет вам во всех будущих случаях, когда вы можете создать другой класс, который его реализует.

ответил John Demetriou 13 ThuEurope/Moscow2012-12-13T21:43:38+04:00Europe/Moscow12bEurope/MoscowThu, 13 Dec 2012 21:43:38 +0400 2012, 21:43:38
1

Не всегда необходимо определить интерфейс для класса.

Простые объекты, такие как объекты значений, не имеют нескольких реализаций. Их тоже не нужно издеваться. Реализация может быть протестирована сама по себе, а при тестировании других классов, которые зависят от них, можно использовать объект фактического значения.

Помните, что создание интерфейса имеет стоимость. Он должен быть обновлен вдоль реализации, ему нужен дополнительный файл, и некоторые IDE будут иметь проблемы с масштабированием в реализации, а не с интерфейсом.

Итак, я бы определял интерфейсы только для классов более высокого уровня, где вам нужна абстракция от реализации.

Обратите внимание, что с классом вы получаете бесплатный интерфейс. Помимо реализации, класс определяет интерфейс из набора общедоступных методов. Этот интерфейс реализуется всеми производными классами. Это не строго говоря интерфейс, но его можно использовать точно так же. Поэтому я не думаю, что необходимо воссоздать интерфейс, который уже существует под именем класса.

ответил Florian F 25 PM00000020000002631 2014, 14:12:26

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132